Import Android SDK Platform P [4719250]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4719250 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4719250.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I9ec0a12c9251b8449dba0d86b0cfdbcca16b0a7c
diff --git a/android/view/DisplayCutout.java b/android/view/DisplayCutout.java
index 66a9c6c..f59c0b5 100644
--- a/android/view/DisplayCutout.java
+++ b/android/view/DisplayCutout.java
@@ -31,6 +31,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.PathParser;
 import android.util.proto.ProtoOutputStream;
 
@@ -75,15 +76,19 @@
             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 DisplayCutout sCachedCutout;
+    private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
 
     private final Rect mSafeInsets;
     private final Region mBounds;
@@ -347,7 +352,7 @@
     }
 
     /**
-     * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout.
+     * Creates the bounding path according to @android:string/config_mainBuiltInDisplayCutout.
      *
      * @hide
      */
@@ -357,6 +362,16 @@
     }
 
     /**
+     * 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),
+                displayWidth, displayHeight, res.getDisplayMetrics().density).first;
+    }
+
+    /**
      * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec.
      *
      * @hide
@@ -364,11 +379,17 @@
     @VisibleForTesting(visibility = PRIVATE)
     public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
             float density) {
+        return pathAndDisplayCutoutFromSpec(spec, displayWidth, displayHeight, density).second;
+    }
+
+    private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
+            int displayWidth, int displayHeight, float density) {
         if (TextUtils.isEmpty(spec)) {
-            return null;
+            return NULL_PAIR;
         }
         synchronized (CACHE_LOCK) {
             if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
+                    && sCachedDisplayHeight == displayHeight
                     && sCachedDensity == density) {
                 return sCachedCutout;
             }
@@ -398,7 +419,7 @@
             p = PathParser.createPathFromPathData(spec);
         } catch (Throwable e) {
             Log.wtf(TAG, "Could not inflate cutout: ", e);
-            return null;
+            return NULL_PAIR;
         }
 
         final Matrix m = new Matrix();
@@ -414,7 +435,7 @@
                 bottomPath = PathParser.createPathFromPathData(bottomSpec);
             } catch (Throwable e) {
                 Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
-                return null;
+                return NULL_PAIR;
             }
             // Keep top transform
             m.postTranslate(0, displayHeight);
@@ -422,10 +443,11 @@
             p.addPath(bottomPath);
         }
 
-        final DisplayCutout result = fromBounds(p);
+        final Pair<Path, DisplayCutout> result = new Pair<>(p, fromBounds(p));
         synchronized (CACHE_LOCK) {
             sCachedSpec = spec;
             sCachedDisplayWidth = displayWidth;
+            sCachedDisplayHeight = displayHeight;
             sCachedDensity = density;
             sCachedCutout = result;
         }
diff --git a/android/view/HapticFeedbackConstants.java b/android/view/HapticFeedbackConstants.java
index b147928..db01cea 100644
--- a/android/view/HapticFeedbackConstants.java
+++ b/android/view/HapticFeedbackConstants.java
@@ -77,6 +77,55 @@
     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).
+     * @hide
+     */
+    public static final int GESTURE_START = 12;
+
+    /**
+     * The user has finished a gesture (e.g. on the soft keyboard).
+     * @hide
+     */
+    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.
+     * @hide
+     */
+    public static final int CONFIRM = 16;
+
+    /**
+     * A haptic effect to signal the rejection or failure of a user interaction.
+     * @hide
+     */
+    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
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index d4610a5..5deee11 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -87,6 +87,7 @@
     private static native void nativeMergeTransaction(long transactionObj,
             long otherTransactionObj);
     private static native void nativeSetAnimationTransaction(long transactionObj);
+    private static native void nativeSetEarlyWakeup(long transactionObj);
 
     private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
     private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
@@ -1642,6 +1643,19 @@
         }
 
         /**
+         * Indicate that SurfaceFlinger should wake up earlier than usual as a result of this
+         * transaction. This should be used when the caller thinks that the scene is complex enough
+         * that it's likely to hit GL composition, and thus, SurfaceFlinger needs to more time in
+         * order not to miss frame deadlines.
+         * <p>
+         * Corresponds to setting ISurfaceComposer::eEarlyWakeup
+         */
+        public Transaction setEarlyWakeup() {
+            nativeSetEarlyWakeup(mNativeObject);
+            return this;
+        }
+
+        /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
          */
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af4..7e54647 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1237 @@
 
 package android.view;
 
-import com.android.layoutlib.bridge.MockView;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
 
 import android.content.Context;
+import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * 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.
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
  *
- * TODO: generate automatically.
+ * <p>The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed. The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it. This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
  *
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
+ *
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
+ *
+ * <p>The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ *
+ * <p>One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render into the screen. If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ *
+ * <ul>
+ * <li> All SurfaceView and
+ * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
+ * from the thread running the SurfaceView's window (typically the main thread
+ * of the application). They thus need to correctly synchronize with any
+ * state that is also touched by the drawing thread.
+ * <li> You must ensure that the drawing thread only touches the underlying
+ * Surface while it is valid -- between
+ * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
+ * and
+ * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
+ * </ul>
+ *
+ * <p class="note"><strong>Note:</strong> Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
+ * updated synchronously with other View rendering. This means that translating
+ * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
+ * artifacts may occur on previous versions of the platform when its window is
+ * positioned asynchronously.</p>
  */
-public class SurfaceView extends MockView {
+public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
+    private static final String TAG = "SurfaceView";
+    private static final boolean DEBUG = false;
+
+    final ArrayList<SurfaceHolder.Callback> mCallbacks
+            = new ArrayList<SurfaceHolder.Callback>();
+
+    final int[] mLocation = new int[2];
+
+    final ReentrantLock mSurfaceLock = new ReentrantLock();
+    final Surface mSurface = new Surface();       // Current surface in use
+    boolean mDrawingStopped = true;
+    // We use this to track if the application has produced a frame
+    // in to the Surface. Up until that point, we should be careful not to punch
+    // holes.
+    boolean mDrawFinished = false;
+
+    final Rect mScreenRect = new Rect();
+    SurfaceSession mSurfaceSession;
+
+    SurfaceControlWithBackground mSurfaceControl;
+    // In the case of format changes we switch out the surface in-place
+    // we need to preserve the old one until the new one has drawn.
+    SurfaceControl mDeferredDestroySurfaceControl;
+    final Rect mTmpRect = new Rect();
+    final Configuration mConfiguration = new Configuration();
+
+    int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+
+    boolean mIsCreating = false;
+    private volatile boolean mRtHandlingPositionUpdates = false;
+
+    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+            = new ViewTreeObserver.OnScrollChangedListener() {
+                    @Override
+                    public void onScrollChanged() {
+                        updateSurface();
+                    }
+            };
+
+    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    // reposition ourselves where the surface is
+                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
+                    updateSurface();
+                    return true;
+                }
+            };
+
+    boolean mRequestedVisible = false;
+    boolean mWindowVisibility = false;
+    boolean mLastWindowVisibility = false;
+    boolean mViewVisibility = false;
+    boolean mWindowStopped = false;
+
+    int mRequestedWidth = -1;
+    int mRequestedHeight = -1;
+    /* Set SurfaceView's format to 565 by default to maintain backward
+     * compatibility with applications assuming this format.
+     */
+    int mRequestedFormat = PixelFormat.RGB_565;
+
+    boolean mHaveFrame = false;
+    boolean mSurfaceCreated = false;
+    long mLastLockTime = 0;
+
+    boolean mVisible = false;
+    int mWindowSpaceLeft = -1;
+    int mWindowSpaceTop = -1;
+    int mSurfaceWidth = -1;
+    int mSurfaceHeight = -1;
+    int mFormat = -1;
+    final Rect mSurfaceFrame = new Rect();
+    int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+    private Translator mTranslator;
+
+    private boolean mGlobalListenersAdded;
+    private boolean mAttachedToWindow;
+
+    private int mSurfaceFlags = SurfaceControl.HIDDEN;
+
+    private int mPendingReportDraws;
+
+    private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
 
     public SurfaceView(Context context) {
         this(context, null);
     }
 
     public SurfaceView(Context context, AttributeSet attrs) {
-        this(context, attrs , 0);
+        this(context, attrs, 0);
     }
 
-    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
 
     public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mRenderNode.requestPositionUpdates(this);
+
+        setWillNotDraw(true);
     }
 
-    public boolean gatherTransparentRegion(Region region) {
-      return false;
-    }
-
-    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
-    }
-
-    public void setZOrderOnTop(boolean onTop) {
-    }
-
-    public void setSecure(boolean isSecure) {
-    }
-
+    /**
+     * Return the SurfaceHolder providing access and control over this
+     * SurfaceView's underlying surface.
+     *
+     * @return SurfaceHolder The holder of the surface.
+     */
     public SurfaceHolder getHolder() {
         return mSurfaceHolder;
     }
 
-    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+    private void updateRequestedVisibility() {
+        mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
+    }
+
+    /** @hide */
+    @Override
+    public void windowStopped(boolean stopped) {
+        mWindowStopped = stopped;
+        updateRequestedVisibility();
+        updateSurface();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        getViewRootImpl().addWindowStoppedCallback(this);
+        mWindowStopped = false;
+
+        mViewVisibility = getVisibility() == VISIBLE;
+        updateRequestedVisibility();
+
+        mAttachedToWindow = true;
+        mParent.requestTransparentRegion(SurfaceView.this);
+        if (!mGlobalListenersAdded) {
+            ViewTreeObserver observer = getViewTreeObserver();
+            observer.addOnScrollChangedListener(mScrollChangedListener);
+            observer.addOnPreDrawListener(mDrawListener);
+            mGlobalListenersAdded = true;
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mWindowVisibility = visibility == VISIBLE;
+        updateRequestedVisibility();
+        updateSurface();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        mViewVisibility = visibility == VISIBLE;
+        boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
+        if (newRequestedVisible != mRequestedVisible) {
+            // our base class (View) invalidates the layout only when
+            // we go from/to the GONE state. However, SurfaceView needs
+            // to request a re-layout when the visibility changes at all.
+            // This is needed because the transparent region is computed
+            // as part of the layout phase, and it changes (obviously) when
+            // the visibility changes.
+            requestLayout();
+        }
+        mRequestedVisible = newRequestedVisible;
+        updateSurface();
+    }
+
+    private void performDrawFinished() {
+        if (mPendingReportDraws > 0) {
+            mDrawFinished = true;
+            if (mAttachedToWindow) {
+                notifyDrawFinished();
+                invalidate();
+            }
+        } else {
+            Log.e(TAG, System.identityHashCode(this) + "finished drawing"
+                    + " but no pending report draw (extra call"
+                    + " to draw completion runnable?)");
+        }
+    }
+
+    void notifyDrawFinished() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot != null) {
+            viewRoot.pendingDrawFinished();
+        }
+        mPendingReportDraws--;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        // It's possible to create a SurfaceView using the default constructor and never
+        // attach it to a view hierarchy, this is a common use case when dealing with
+        // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
+        // the lifecycle. Instead of attaching it to a view, he/she can just pass
+        // the SurfaceHolder forward, most live wallpapers do it.
+        if (viewRoot != null) {
+            viewRoot.removeWindowStoppedCallback(this);
+        }
+
+        mAttachedToWindow = false;
+        if (mGlobalListenersAdded) {
+            ViewTreeObserver observer = getViewTreeObserver();
+            observer.removeOnScrollChangedListener(mScrollChangedListener);
+            observer.removeOnPreDrawListener(mDrawListener);
+            mGlobalListenersAdded = false;
+        }
+
+        while (mPendingReportDraws > 0) {
+            notifyDrawFinished();
+        }
+
+        mRequestedVisible = false;
+
+        updateSurface();
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+        }
+        mSurfaceControl = null;
+
+        mHaveFrame = false;
+
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = mRequestedWidth >= 0
+                ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
+                : getDefaultSize(0, widthMeasureSpec);
+        int height = mRequestedHeight >= 0
+                ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
+                : getDefaultSize(0, heightMeasureSpec);
+        setMeasuredDimension(width, height);
+    }
+
+    /** @hide */
+    @Override
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        boolean result = super.setFrame(left, top, right, bottom);
+        updateSurface();
+        return result;
+    }
+
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        if (isAboveParent() || !mDrawFinished) {
+            return super.gatherTransparentRegion(region);
+        }
+
+        boolean opaque = true;
+        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+            // this view draws, remove it from the transparent region
+            opaque = super.gatherTransparentRegion(region);
+        } else if (region != null) {
+            int w = getWidth();
+            int h = getHeight();
+            if (w>0 && h>0) {
+                getLocationInWindow(mLocation);
+                // otherwise, punch a hole in the whole hierarchy
+                int l = mLocation[0];
+                int t = mLocation[1];
+                region.op(l, t, l+w, t+h, Region.Op.UNION);
+            }
+        }
+        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+            opaque = false;
+        }
+        return opaque;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mDrawFinished && !isAboveParent()) {
+            // draw() is not called when SKIP_DRAW is set
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+                // punch a whole in the view-hierarchy below us
+                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+            }
+        }
+        super.draw(canvas);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mDrawFinished && !isAboveParent()) {
+            // draw() is not called when SKIP_DRAW is set
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+                // punch a whole in the view-hierarchy below us
+                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+            }
+        }
+        super.dispatchDraw(canvas);
+    }
+
+    /**
+     * Control whether the surface view's surface is placed on top of another
+     * regular surface view in the window (but still behind the window itself).
+     * This is typically used to place overlays on top of an underlying media
+     * surface view.
+     *
+     * <p>Note that this must be set before the surface view's containing
+     * window is attached to the window manager.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+     */
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+        mSubLayer = isMediaOverlay
+            ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
+    }
+
+    /**
+     * Control whether the surface view's surface is placed on top of its
+     * window.  Normally it is placed behind the window, to allow it to
+     * (for the most part) appear to composite with the views in the
+     * hierarchy.  By setting this, you cause it to be placed above the
+     * window.  This means that none of the contents of the window this
+     * SurfaceView is in will be visible on top of its surface.
+     *
+     * <p>Note that this must be set before the surface view's containing
+     * window is attached to the window manager.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+     */
+    public void setZOrderOnTop(boolean onTop) {
+        if (onTop) {
+            mSubLayer = APPLICATION_PANEL_SUBLAYER;
+        } else {
+            mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+        }
+    }
+
+    /**
+     * Control whether the surface view's content should be treated as secure,
+     * preventing it from appearing in screenshots or from being viewed on
+     * non-secure displays.
+     *
+     * <p>Note that this must be set before the surface view's containing
+     * window is attached to the window manager.
+     *
+     * <p>See {@link android.view.Display#FLAG_SECURE} for details.
+     *
+     * @param isSecure True if the surface view is secure.
+     */
+    public void setSecure(boolean isSecure) {
+        if (isSecure) {
+            mSurfaceFlags |= SurfaceControl.SECURE;
+        } else {
+            mSurfaceFlags &= ~SurfaceControl.SECURE;
+        }
+    }
+
+    private void updateOpaqueFlag() {
+        if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+            mSurfaceFlags |= SurfaceControl.OPAQUE;
+        } else {
+            mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+        }
+    }
+
+    private Rect getParentSurfaceInsets() {
+        final ViewRootImpl root = getViewRootImpl();
+        if (root == null) {
+            return null;
+        } else {
+            return root.mWindowAttributes.surfaceInsets;
+        }
+    }
+
+    /** @hide */
+    protected void updateSurface() {
+        if (!mHaveFrame) {
+            return;
+        }
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+            return;
+        }
+
+        mTranslator = viewRoot.mTranslator;
+        if (mTranslator != null) {
+            mSurface.setCompatibilityTranslator(mTranslator);
+        }
+
+        int myWidth = mRequestedWidth;
+        if (myWidth <= 0) myWidth = getWidth();
+        int myHeight = mRequestedHeight;
+        if (myHeight <= 0) myHeight = getHeight();
+
+        final boolean formatChanged = mFormat != mRequestedFormat;
+        final boolean visibleChanged = mVisible != mRequestedVisible;
+        final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
+                && mRequestedVisible;
+        final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+        final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+        boolean redrawNeeded = false;
+
+        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+            getLocationInWindow(mLocation);
+
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                    + "Changes: creating=" + creating
+                    + " format=" + formatChanged + " size=" + sizeChanged
+                    + " visible=" + visibleChanged
+                    + " left=" + (mWindowSpaceLeft != mLocation[0])
+                    + " top=" + (mWindowSpaceTop != mLocation[1]));
+
+            try {
+                final boolean visible = mVisible = mRequestedVisible;
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                mSurfaceWidth = myWidth;
+                mSurfaceHeight = myHeight;
+                mFormat = mRequestedFormat;
+                mLastWindowVisibility = mWindowVisibility;
+
+                mScreenRect.left = mWindowSpaceLeft;
+                mScreenRect.top = mWindowSpaceTop;
+                mScreenRect.right = mWindowSpaceLeft + getWidth();
+                mScreenRect.bottom = mWindowSpaceTop + getHeight();
+                if (mTranslator != null) {
+                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+                }
+
+                final Rect surfaceInsets = getParentSurfaceInsets();
+                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+                if (creating) {
+                    mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+                    mDeferredDestroySurfaceControl = mSurfaceControl;
+
+                    updateOpaqueFlag();
+                    final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+                    mSurfaceControl = new SurfaceControlWithBackground(
+                            name,
+                            (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
+                            new SurfaceControl.Builder(mSurfaceSession)
+                                    .setSize(mSurfaceWidth, mSurfaceHeight)
+                                    .setFormat(mFormat)
+                                    .setFlags(mSurfaceFlags));
+                } else if (mSurfaceControl == null) {
+                    return;
+                }
+
+                boolean realSizeChanged = false;
+
+                mSurfaceLock.lock();
+                try {
+                    mDrawingStopped = !visible;
+
+                    if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                            + "Cur surface: " + mSurface);
+
+                    SurfaceControl.openTransaction();
+                    try {
+                        mSurfaceControl.setLayer(mSubLayer);
+                        if (mViewVisibility) {
+                            mSurfaceControl.show();
+                        } else {
+                            mSurfaceControl.hide();
+                        }
+
+                        // While creating the surface, we will set it's initial
+                        // geometry. Outside of that though, we should generally
+                        // leave it to the RenderThread.
+                        //
+                        // There is one more case when the buffer size changes we aren't yet
+                        // prepared to sync (as even following the transaction applying
+                        // we still need to latch a buffer).
+                        // b/28866173
+                        if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+                            mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+                            mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+                                    0.0f, 0.0f,
+                                    mScreenRect.height() / (float) mSurfaceHeight);
+                        }
+                        if (sizeChanged) {
+                            mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+                        }
+                    } finally {
+                        SurfaceControl.closeTransaction();
+                    }
+
+                    if (sizeChanged || creating) {
+                        redrawNeeded = true;
+                    }
+
+                    mSurfaceFrame.left = 0;
+                    mSurfaceFrame.top = 0;
+                    if (mTranslator == null) {
+                        mSurfaceFrame.right = mSurfaceWidth;
+                        mSurfaceFrame.bottom = mSurfaceHeight;
+                    } else {
+                        float appInvertedScale = mTranslator.applicationInvertedScale;
+                        mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+                        mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+                    }
+
+                    final int surfaceWidth = mSurfaceFrame.right;
+                    final int surfaceHeight = mSurfaceFrame.bottom;
+                    realSizeChanged = mLastSurfaceWidth != surfaceWidth
+                            || mLastSurfaceHeight != surfaceHeight;
+                    mLastSurfaceWidth = surfaceWidth;
+                    mLastSurfaceHeight = surfaceHeight;
+                } finally {
+                    mSurfaceLock.unlock();
+                }
+
+                try {
+                    redrawNeeded |= visible && !mDrawFinished;
+
+                    SurfaceHolder.Callback callbacks[] = null;
+
+                    final boolean surfaceChanged = creating;
+                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
+                        mSurfaceCreated = false;
+                        if (mSurface.isValid()) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceDestroyed");
+                            callbacks = getSurfaceCallbacks();
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceDestroyed(mSurfaceHolder);
+                            }
+                            // Since Android N the same surface may be reused and given to us
+                            // again by the system server at a later point. However
+                            // as we didn't do this in previous releases, clients weren't
+                            // necessarily required to clean up properly in
+                            // surfaceDestroyed. This leads to problems for example when
+                            // clients don't destroy their EGL context, and try
+                            // and create a new one on the same surface following reuse.
+                            // Since there is no valid use of the surface in-between
+                            // surfaceDestroyed and surfaceCreated, we force a disconnect,
+                            // so the next connect will always work if we end up reusing
+                            // the surface.
+                            if (mSurface.isValid()) {
+                                mSurface.forceScopedDisconnect();
+                            }
+                        }
+                    }
+
+                    if (creating) {
+                        mSurface.copyFrom(mSurfaceControl);
+                    }
+
+                    if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
+                            < Build.VERSION_CODES.O) {
+                        // Some legacy applications use the underlying native {@link Surface} object
+                        // as a key to whether anything has changed. In these cases, updates to the
+                        // existing {@link Surface} will be ignored when the size changes.
+                        // Therefore, we must explicitly recreate the {@link Surface} in these
+                        // cases.
+                        mSurface.createFrom(mSurfaceControl);
+                    }
+
+                    if (visible && mSurface.isValid()) {
+                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+                            mSurfaceCreated = true;
+                            mIsCreating = true;
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceCreated");
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceCreated(mSurfaceHolder);
+                            }
+                        }
+                        if (creating || formatChanged || sizeChanged
+                                || visibleChanged || realSizeChanged) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceChanged -- format=" + mFormat
+                                    + " w=" + myWidth + " h=" + myHeight);
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                            }
+                        }
+                        if (redrawNeeded) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceRedrawNeeded");
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+
+                            mPendingReportDraws++;
+                            viewRoot.drawPending();
+                            SurfaceCallbackHelper sch =
+                                    new SurfaceCallbackHelper(this::onDrawFinished);
+                            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+                        }
+                    }
+                } finally {
+                    mIsCreating = false;
+                    if (mSurfaceControl != null && !mSurfaceCreated) {
+                        mSurface.release();
+                        // If we are not in the stopped state, then the destruction of the Surface
+                        // represents a visual change we need to display, and we should go ahead
+                        // and destroy the SurfaceControl. However if we are in the stopped state,
+                        // we can just leave the Surface around so it can be a part of animations,
+                        // and we let the life-time be tied to the parent surface.
+                        if (!mWindowStopped) {
+                            mSurfaceControl.destroy();
+                            mSurfaceControl = null;
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception configuring surface", ex);
+            }
+            if (DEBUG) Log.v(
+                TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+                + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+                + ", frame=" + mSurfaceFrame);
+        } else {
+            // Calculate the window position in case RT loses the window
+            // and we need to fallback to a UI-thread driven position update
+            getLocationInSurface(mLocation);
+            final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+                    || mWindowSpaceTop != mLocation[1];
+            final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+                    || getHeight() != mScreenRect.height();
+            if (positionChanged || layoutSizeChanged) { // Only the position has changed
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+                // in view local space.
+                mLocation[0] = getWidth();
+                mLocation[1] = getHeight();
+
+                mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+                        mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
+
+                if (mTranslator != null) {
+                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+                }
+
+                if (mSurfaceControl == null) {
+                    return;
+                }
+
+                if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
+                    try {
+                        if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+                                "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+                                mScreenRect.left, mScreenRect.top,
+                                mScreenRect.right, mScreenRect.bottom));
+                        setParentSpaceRectangle(mScreenRect, -1);
+                    } catch (Exception ex) {
+                        Log.e(TAG, "Exception configuring surface", ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private void onDrawFinished() {
+        if (DEBUG) {
+            Log.i(TAG, System.identityHashCode(this) + " "
+                    + "finishedDrawing");
+        }
+
+        if (mDeferredDestroySurfaceControl != null) {
+            mDeferredDestroySurfaceControl.destroy();
+            mDeferredDestroySurfaceControl = null;
+        }
+
+        runOnUiThread(() -> {
+            performDrawFinished();
+        });
+    }
+
+    /**
+     * A place to over-ride for applying child-surface transactions.
+     * These can be synchronized with the viewroot surface using deferTransaction.
+     *
+     * Called from RenderWorker while UI thread is paused.
+     * @hide
+     */
+    protected void applyChildSurfaceTransaction_renderWorker(SurfaceControl.Transaction t,
+            Surface viewRootSurface, long nextViewRootFrameNumber) {
+    }
+
+    private void applySurfaceTransforms(SurfaceControl surface, Rect position, long frameNumber) {
+        if (frameNumber > 0) {
+            final ViewRootImpl viewRoot = getViewRootImpl();
+
+            mRtTransaction.deferTransactionUntilSurface(surface, viewRoot.mSurface,
+                    frameNumber);
+        }
+
+        mRtTransaction.setPosition(surface, position.left, position.top);
+        mRtTransaction.setMatrix(surface,
+                position.width() / (float) mSurfaceWidth,
+                0.0f, 0.0f,
+                position.height() / (float) mSurfaceHeight);
+    }
+
+    private void setParentSpaceRectangle(Rect position, long frameNumber) {
+        final ViewRootImpl viewRoot = getViewRootImpl();
+
+        applySurfaceTransforms(mSurfaceControl, position, frameNumber);
+        applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber);
+
+        applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface,
+                frameNumber);
+
+        mRtTransaction.apply();
+    }
+
+    private Rect mRTLastReportedPosition = new Rect();
+
+    /**
+     * Called by native by a Rendering Worker thread to update the window position
+     * @hide
+     */
+    public final void updateSurfacePosition_renderWorker(long frameNumber,
+            int left, int top, int right, int bottom) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+        // its 2nd frame if RenderThread is running slowly could potentially see
+        // this as false, enter the branch, get pre-empted, then this comes along
+        // and reports a new position, then the UI thread resumes and reports
+        // its position. This could therefore be de-sync'd in that interval, but
+        // the synchronization would violate the rule that RT must never block
+        // on the UI thread which would open up potential deadlocks. The risk of
+        // a single-frame desync is therefore preferable for now.
+        mRtHandlingPositionUpdates = true;
+        if (mRTLastReportedPosition.left == left
+                && mRTLastReportedPosition.top == top
+                && mRTLastReportedPosition.right == right
+                && mRTLastReportedPosition.bottom == bottom) {
+            return;
+        }
+        try {
+            if (DEBUG) {
+                Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+                        "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+                        frameNumber, left, top, right, bottom));
+            }
+            mRTLastReportedPosition.set(left, top, right, bottom);
+            setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+            // Now overwrite mRTLastReportedPosition with our values
+        } catch (Exception ex) {
+            Log.e(TAG, "Exception from repositionChild", ex);
+        }
+    }
+
+    /**
+     * Called by native on RenderThread to notify that the view is no longer in the
+     * draw tree. UI thread is blocked at this point.
+     * @hide
+     */
+    public final void surfacePositionLost_uiRtSync(long frameNumber) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
+                    System.identityHashCode(this), frameNumber));
+        }
+        mRTLastReportedPosition.setEmpty();
+
+        if (mSurfaceControl == null) {
+            return;
+        }
+        if (mRtHandlingPositionUpdates) {
+            mRtHandlingPositionUpdates = false;
+            // This callback will happen while the UI thread is blocked, so we can
+            // safely access other member variables at this time.
+            // So do what the UI thread would have done if RT wasn't handling position
+            // updates.
+            if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+                try {
+                    if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+                            "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+                            mScreenRect.left, mScreenRect.top,
+                            mScreenRect.right, mScreenRect.bottom));
+                    setParentSpaceRectangle(mScreenRect, frameNumber);
+                } catch (Exception ex) {
+                    Log.e(TAG, "Exception configuring surface", ex);
+                }
+            }
+        }
+    }
+
+    private SurfaceHolder.Callback[] getSurfaceCallbacks() {
+        SurfaceHolder.Callback callbacks[];
+        synchronized (mCallbacks) {
+            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+            mCallbacks.toArray(callbacks);
+        }
+        return callbacks;
+    }
+
+    private void runOnUiThread(Runnable runnable) {
+        Handler handler = getHandler();
+        if (handler != null && handler.getLooper() != Looper.myLooper()) {
+            handler.post(runnable);
+        } else {
+            runnable.run();
+        }
+    }
+
+    /**
+     * Check to see if the surface has fixed size dimensions or if the surface's
+     * dimensions are dimensions are dependent on its current layout.
+     *
+     * @return true if the surface has dimensions that are fixed in size
+     * @hide
+     */
+    public boolean isFixedSize() {
+        return (mRequestedWidth != -1 || mRequestedHeight != -1);
+    }
+
+    private boolean isAboveParent() {
+        return mSubLayer >= 0;
+    }
+
+    /**
+     * Set an opaque background color to use with this {@link SurfaceView} when it's being resized
+     * and size of the content hasn't updated yet. This color will fill the expanded area when the
+     * view becomes larger.
+     * @param bgColor An opaque color to fill the background. Alpha component will be ignored.
+     * @hide
+     */
+    public void setResizeBackgroundColor(int bgColor) {
+        mSurfaceControl.setBackgroundColor(bgColor);
+    }
+
+    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+        private static final String LOG_TAG = "SurfaceHolder";
 
         @Override
         public boolean isCreating() {
-            return false;
+            return mIsCreating;
         }
 
         @Override
         public void addCallback(Callback callback) {
+            synchronized (mCallbacks) {
+                // This is a linear search, but in practice we'll
+                // have only a couple callbacks, so it doesn't matter.
+                if (mCallbacks.contains(callback) == false) {
+                    mCallbacks.add(callback);
+                }
+            }
         }
 
         @Override
         public void removeCallback(Callback callback) {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(callback);
+            }
         }
 
         @Override
         public void setFixedSize(int width, int height) {
+            if (mRequestedWidth != width || mRequestedHeight != height) {
+                mRequestedWidth = width;
+                mRequestedHeight = height;
+                requestLayout();
+            }
         }
 
         @Override
         public void setSizeFromLayout() {
+            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+                mRequestedWidth = mRequestedHeight = -1;
+                requestLayout();
+            }
         }
 
         @Override
         public void setFormat(int format) {
+            // for backward compatibility reason, OPAQUE always
+            // means 565 for SurfaceView
+            if (format == PixelFormat.OPAQUE)
+                format = PixelFormat.RGB_565;
+
+            mRequestedFormat = format;
+            if (mSurfaceControl != null) {
+                updateSurface();
+            }
         }
 
+        /**
+         * @deprecated setType is now ignored.
+         */
         @Override
-        public void setType(int type) {
-        }
+        @Deprecated
+        public void setType(int type) { }
 
         @Override
         public void setKeepScreenOn(boolean screenOn) {
+            runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
         }
 
+        /**
+         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+         *
+         * After drawing into the provided {@link Canvas}, the caller must
+         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+         *
+         * The caller must redraw the entire surface.
+         * @return A canvas for drawing into the surface.
+         */
         @Override
         public Canvas lockCanvas() {
-            return null;
+            return internalLockCanvas(null, false);
+        }
+
+        /**
+         * Gets a {@link Canvas} for drawing into the SurfaceView's 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.
+         */
+        @Override
+        public Canvas lockCanvas(Rect inOutDirty) {
+            return internalLockCanvas(inOutDirty, false);
         }
 
         @Override
-        public Canvas lockCanvas(Rect dirty) {
+        public Canvas lockHardwareCanvas() {
+            return internalLockCanvas(null, true);
+        }
+
+        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
+            mSurfaceLock.lock();
+
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+
+            Canvas c = null;
+            if (!mDrawingStopped && mSurfaceControl != null) {
+                try {
+                    if (hardware) {
+                        c = mSurface.lockHardwareCanvas();
+                    } else {
+                        c = mSurface.lockCanvas(dirty);
+                    }
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "Exception locking surface", e);
+                }
+            }
+
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
+            if (c != null) {
+                mLastLockTime = SystemClock.uptimeMillis();
+                return c;
+            }
+
+            // If the Surface is not ready to be drawn, then return null,
+            // but throttle calls to this function so it isn't called more
+            // than every 100ms.
+            long now = SystemClock.uptimeMillis();
+            long nextTime = mLastLockTime + 100;
+            if (nextTime > now) {
+                try {
+                    Thread.sleep(nextTime-now);
+                } catch (InterruptedException e) {
+                }
+                now = SystemClock.uptimeMillis();
+            }
+            mLastLockTime = now;
+            mSurfaceLock.unlock();
+
             return null;
         }
 
+        /**
+         * 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}.
+         */
         @Override
         public void unlockCanvasAndPost(Canvas canvas) {
+            mSurface.unlockCanvasAndPost(canvas);
+            mSurfaceLock.unlock();
         }
 
         @Override
         public Surface getSurface() {
-            return null;
+            return mSurface;
         }
 
         @Override
         public Rect getSurfaceFrame() {
-            return null;
+            return mSurfaceFrame;
         }
     };
-}
 
+    class SurfaceControlWithBackground extends SurfaceControl {
+        SurfaceControl mBackgroundControl;
+        private boolean mOpaque = true;
+        public boolean mVisible = false;
+
+        public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
+                       throws Exception {
+            super(b.setName(name).build());
+
+            mBackgroundControl = b.setName("Background for -" + name)
+                    .setFormat(OPAQUE)
+                    .setColorLayer(true)
+                    .build();
+            mOpaque = opaque;
+        }
+
+        @Override
+        public void setAlpha(float alpha) {
+            super.setAlpha(alpha);
+            mBackgroundControl.setAlpha(alpha);
+        }
+
+        @Override
+        public void setLayer(int zorder) {
+            super.setLayer(zorder);
+            // -3 is below all other child layers as SurfaceView never goes below -2
+            mBackgroundControl.setLayer(-3);
+        }
+
+        @Override
+        public void setPosition(float x, float y) {
+            super.setPosition(x, y);
+            mBackgroundControl.setPosition(x, y);
+        }
+
+        @Override
+        public void setSize(int w, int h) {
+            super.setSize(w, h);
+            mBackgroundControl.setSize(w, h);
+        }
+
+        @Override
+        public void setWindowCrop(Rect crop) {
+            super.setWindowCrop(crop);
+            mBackgroundControl.setWindowCrop(crop);
+        }
+
+        @Override
+        public void setFinalCrop(Rect crop) {
+            super.setFinalCrop(crop);
+            mBackgroundControl.setFinalCrop(crop);
+        }
+
+        @Override
+        public void setLayerStack(int layerStack) {
+            super.setLayerStack(layerStack);
+            mBackgroundControl.setLayerStack(layerStack);
+        }
+
+        @Override
+        public void setOpaque(boolean isOpaque) {
+            super.setOpaque(isOpaque);
+            mOpaque = isOpaque;
+            updateBackgroundVisibility();
+        }
+
+        @Override
+        public void setSecure(boolean isSecure) {
+            super.setSecure(isSecure);
+        }
+
+        @Override
+        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+            mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
+        }
+
+        @Override
+        public void hide() {
+            super.hide();
+            mVisible = false;
+            updateBackgroundVisibility();
+        }
+
+        @Override
+        public void show() {
+            super.show();
+            mVisible = true;
+            updateBackgroundVisibility();
+        }
+
+        @Override
+        public void destroy() {
+            super.destroy();
+            mBackgroundControl.destroy();
+         }
+
+        @Override
+        public void release() {
+            super.release();
+            mBackgroundControl.release();
+        }
+
+        @Override
+        public void setTransparentRegionHint(Region region) {
+            super.setTransparentRegionHint(region);
+            mBackgroundControl.setTransparentRegionHint(region);
+        }
+
+        @Override
+        public void deferTransactionUntil(IBinder handle, long frame) {
+            super.deferTransactionUntil(handle, frame);
+            mBackgroundControl.deferTransactionUntil(handle, frame);
+        }
+
+        @Override
+        public void deferTransactionUntil(Surface barrier, long frame) {
+            super.deferTransactionUntil(barrier, frame);
+            mBackgroundControl.deferTransactionUntil(barrier, frame);
+        }
+
+        /** Set the color to fill the background with. */
+        private void setBackgroundColor(int bgColor) {
+            final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+                    Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+            SurfaceControl.openTransaction();
+            try {
+                mBackgroundControl.setColor(colorComponents);
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+        }
+
+        void updateBackgroundVisibility() {
+            if (mOpaque && mVisible) {
+                mBackgroundControl.show();
+            } else {
+                mBackgroundControl.hide();
+            }
+        }
+    }
+}
diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java
index 5eb7e9c..e03f5fa 100644
--- a/android/view/ThreadedRenderer.java
+++ b/android/view/ThreadedRenderer.java
@@ -190,6 +190,10 @@
      */
     public static final String DEBUG_FPS_DIVISOR = "debug.hwui.fps_divisor";
 
+    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;
+
     static {
         // Try to check OpenGL support early if possible.
         isAvailable();
@@ -1140,6 +1144,16 @@
         nHackySetRTAnimationsEnabled(divisor <= 1);
     }
 
+    /**
+     * Changes the OpenGL context priority if IMG_context_priority extension is available. Must be
+     * called before any OpenGL context is created.
+     *
+     * @param priority The priority to use. Must be one of EGL_CONTEXT_PRIORITY_* values.
+     */
+    public static void setContextPriority(int priority) {
+        nSetContextPriority(priority);
+    }
+
     /** Not actually public - internal use only. This doc to make lint happy */
     public static native void disableVsync();
 
@@ -1213,4 +1227,5 @@
     private static native void nHackySetRTAnimationsEnabled(boolean enabled);
     private static native void nSetDebuggingEnabled(boolean enabled);
     private static native void nSetIsolatedProcess(boolean enabled);
+    private static native void nSetContextPriority(int priority);
 }
diff --git a/android/view/View.java b/android/view/View.java
index 97e11b1..71b6084 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -697,6 +697,7 @@
  * security policy. See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
  * </p>
  *
+ * @attr ref android.R.styleable#View_accessibilityHeading
  * @attr ref android.R.styleable#View_alpha
  * @attr ref android.R.styleable#View_background
  * @attr ref android.R.styleable#View_clickable
@@ -2955,7 +2956,7 @@
      *     1                             PFLAG3_SCREEN_READER_FOCUSABLE
      *    1                              PFLAG3_AGGREGATED_VISIBLE
      *   1                               PFLAG3_AUTOFILLID_EXPLICITLY_SET
-     *  1                                available
+     *  1                                PFLAG3_ACCESSIBILITY_HEADING
      * |-------|-------|-------|-------|
      */
 
@@ -3252,6 +3253,11 @@
      */
     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 */
 
     /**
@@ -5475,6 +5481,8 @@
                 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));
             }
         }
 
@@ -8795,6 +8803,7 @@
         info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN);
         populateAccessibilityNodeInfoDrawingOrderInParent(info);
         info.setPaneTitle(mAccessibilityPaneTitle);
+        info.setHeading(isAccessibilityHeading());
     }
 
     /**
@@ -10398,7 +10407,21 @@
      *
      * @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);
     }
@@ -10407,8 +10430,22 @@
      * 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;
     }
@@ -10754,11 +10791,37 @@
      *                              accessibility tools.
      */
     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
+     */
+    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 (screenReaderFocusable) {
-            pflags3 |= PFLAG3_SCREEN_READER_FOCUSABLE;
+        if (newValue) {
+            pflags3 |= mask;
         } else {
-            pflags3 &= ~PFLAG3_SCREEN_READER_FOCUSABLE;
+            pflags3 &= ~mask;
         }
 
         if (pflags3 != mPrivateFlags3) {
@@ -11763,6 +11826,14 @@
         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
@@ -14978,10 +15049,7 @@
     public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
         ensureTransformationInfo();
         if (mTransformationInfo.mAlpha != alpha) {
-            // Report visibility changes, which can affect children, to accessibility
-            if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) {
-                notifySubtreeAccessibilityStateChangedIfNeeded();
-            }
+            float oldAlpha = mTransformationInfo.mAlpha;
             mTransformationInfo.mAlpha = alpha;
             if (onSetAlpha((int) (alpha * 255))) {
                 mPrivateFlags |= PFLAG_ALPHA_SET;
@@ -14993,6 +15061,10 @@
                 invalidateViewProperty(true, false);
                 mRenderNode.setAlpha(getFinalAlpha());
             }
+            // Report visibility changes, which can affect children, to accessibility
+            if ((alpha == 0) ^ (oldAlpha == 0)) {
+                notifySubtreeAccessibilityStateChangedIfNeeded();
+            }
         }
     }
 
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
index 6002fe5..2ec42c0 100644
--- a/android/view/ViewGroup.java
+++ b/android/view/ViewGroup.java
@@ -5692,6 +5692,7 @@
         }
         dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE
                 && isShown());
+        notifySubtreeAccessibilityStateChangedIfNeeded();
     }
 
     /**
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 433c90b..730c372 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -3719,7 +3719,7 @@
         checkThread();
         if (mView != null) {
             if (!mView.hasFocus()) {
-                if (sAlwaysAssignFocus || !isInTouchMode()) {
+                if (sAlwaysAssignFocus || !mAttachInfo.mInTouchMode) {
                     v.requestFocus();
                 }
             } else {
@@ -6482,17 +6482,17 @@
                     params.type = mOrigWindowType;
                 }
             }
-
-            if (mSurface.isValid()) {
-                params.frameNumber = mSurface.getNextFrameNumber();
-            }
         }
 
-        int relayoutResult = mWindowSession.relayout(
-                mWindow, mSeq, params,
+        long frameNumber = -1;
+        if (mSurface.isValid()) {
+            frameNumber = mSurface.getNextFrameNumber();
+        }
+
+        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                 (int) (mView.getMeasuredWidth() * appScale + 0.5f),
-                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
-                viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
+                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
+                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                 mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                 mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                 mPendingMergedConfiguration, mSurface);
@@ -8305,6 +8305,12 @@
 
         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() {
@@ -8322,6 +8328,7 @@
                 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;
@@ -8329,6 +8336,7 @@
             // In any case reset to initial state.
             source.resetSubtreeAccessibilityStateChanged();
             mChangeTypes = 0;
+            if (AccessibilityEvent.DEBUG_ORIGIN) mOrigin = null;
         }
 
         public void runOrPost(View source, int changeType) {
@@ -8352,12 +8360,18 @@
                 // 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();
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index f6181d7..0f5c23f 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -2438,13 +2438,6 @@
         public long hideTimeoutMilliseconds = -1;
 
         /**
-         * A frame number in which changes requested in this layout will be rendered.
-         *
-         * @hide
-         */
-        public long frameNumber = -1;
-
-        /**
          * 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
@@ -2617,7 +2610,6 @@
             TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
             out.writeInt(mColorMode);
             out.writeLong(hideTimeoutMilliseconds);
-            out.writeLong(frameNumber);
         }
 
         public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -2674,7 +2666,6 @@
             accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
             mColorMode = in.readInt();
             hideTimeoutMilliseconds = in.readLong();
-            frameNumber = in.readLong();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2875,10 +2866,6 @@
                 changes |= SURFACE_INSETS_CHANGED;
             }
 
-            // The frame number changing is only relevant in the context of other
-            // changes, and so we don't need to track it with a flag.
-            frameNumber = o.frameNumber;
-
             if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) {
                 hasManualSurfaceInsets = o.hasManualSurfaceInsets;
                 changes |= SURFACE_INSETS_CHANGED;
diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java
index cca66d6..08c2d0b 100644
--- a/android/view/WindowManagerGlobal.java
+++ b/android/view/WindowManagerGlobal.java
@@ -610,6 +610,10 @@
                     ViewRootImpl root = mRoots.get(i);
                     // Client might remove the view by "stopped" event.
                     root.setWindowStopped(stopped);
+                    // 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);
                 }
             }
         }
diff --git a/android/view/accessibility/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java
index e0f74a7..7946e9e 100644
--- a/android/view/accessibility/AccessibilityEvent.java
+++ b/android/view/accessibility/AccessibilityEvent.java
@@ -201,6 +201,7 @@
  * <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>
@@ -388,6 +389,8 @@
  */
 public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
     private static final boolean DEBUG = false;
+    /** @hide */
+    public static final boolean DEBUG_ORIGIN = false;
 
     /**
      * Invalid selection/focus position.
@@ -748,7 +751,7 @@
 
     private static final int MAX_POOL_SIZE = 10;
     private static final SynchronizedPool<AccessibilityEvent> sPool =
-            new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
+            new SynchronizedPool<>(MAX_POOL_SIZE);
 
     private @EventType int mEventType;
     private CharSequence mPackageName;
@@ -758,6 +761,17 @@
     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;
 
     /*
@@ -780,6 +794,7 @@
         mWindowChangeTypes = event.mWindowChangeTypes;
         mEventTime = event.mEventTime;
         mPackageName = event.mPackageName;
+        if (DEBUG_ORIGIN) originStackTrace = event.originStackTrace;
     }
 
     /**
@@ -849,16 +864,17 @@
     }
 
     /**
-     * Gets the bit mask of change types signaled by an
-     * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. A single event may represent
-     * multiple change types.
+     * 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 AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
-     *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
-     *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
-     *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_CONTENT_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}
      *         </ul>
      */
     @ContentChangeTypes
@@ -877,6 +893,7 @@
             }
             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";
             default: return Integer.toHexString(type);
         }
@@ -1104,7 +1121,9 @@
      */
     public static AccessibilityEvent obtain() {
         AccessibilityEvent event = sPool.acquire();
-        return (event != null) ? event : new AccessibilityEvent();
+        if (event == null) event = new AccessibilityEvent();
+        if (DEBUG_ORIGIN) event.originStackTrace = Thread.currentThread().getStackTrace();
+        return event;
     }
 
     /**
@@ -1142,6 +1161,7 @@
                 record.recycle();
             }
         }
+        if (DEBUG_ORIGIN) originStackTrace = null;
     }
 
     /**
@@ -1164,7 +1184,7 @@
         // Read the records.
         final int recordCount = parcel.readInt();
         if (recordCount > 0) {
-            mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+            mRecords = new ArrayList<>(recordCount);
             for (int i = 0; i < recordCount; i++) {
                 AccessibilityRecord record = AccessibilityRecord.obtain();
                 readAccessibilityRecordFromParcel(record, parcel);
@@ -1172,6 +1192,17 @@
                 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());
+            }
+        }
     }
 
     /**
@@ -1227,6 +1258,17 @@
             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());
+            }
+        }
     }
 
     /**
@@ -1285,7 +1327,7 @@
         }
         if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) {
             builder.append("; WindowChangeTypes: ").append(
-                    contentChangeTypesToString(mWindowChangeTypes));
+                    windowChangeTypesToString(mWindowChangeTypes));
         }
         super.appendTo(builder);
         if (DEBUG || DEBUG_CONCISE_TOSTRING) {
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
index 72af203..d60c481 100644
--- a/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -326,12 +326,14 @@
                             accessibilityWindowId, accessibilityNodeId);
                     if (cachedInfo != null) {
                         if (DEBUG) {
-                            Log.i(LOG_TAG, "Node cache hit");
+                            Log.i(LOG_TAG, "Node cache hit for "
+                                    + idToString(accessibilityWindowId, accessibilityNodeId));
                         }
                         return cachedInfo;
                     }
                     if (DEBUG) {
-                        Log.i(LOG_TAG, "Node cache miss");
+                        Log.i(LOG_TAG, "Node cache miss for "
+                                + idToString(accessibilityWindowId, accessibilityNodeId));
                     }
                 }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
@@ -368,6 +370,11 @@
         return null;
     }
 
+    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
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 84b4064..cbb23f1 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,48 +16,156 @@
 
 package android.view.accessibility;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+
+import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
 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.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
 import android.view.IWindow;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent.EventType;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IntPair;
+
+import java.util.ArrayList;
 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,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
+ * and provides facilities for querying the accessibility state of the system.
+ * Accessibility 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}.
+ * {@link android.accessibilityservice.AccessibilityService}.
  *
  * @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
  */
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
 public final class AccessibilityManager {
+    private static final boolean DEBUG = false;
 
-    private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+    private static final String LOG_TAG = "AccessibilityManager";
 
+    /** @hide */
+    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+
+    /** @hide */
+    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+
+    /** @hide */
+    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+
+    /** @hide */
+    public static final int DALTONIZER_DISABLED = -1;
+
+    /** @hide */
+    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+    /** @hide */
+    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+    /** @hide */
+    public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
-     * Listener for the accessibility state.
+     * Activity action: Launch UI to manage which accessibility service or feature is assigned
+     * to the navigation bar Accessibility button.
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+            "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
+    static final Object sInstanceSync = new Object();
+
+    private static AccessibilityManager sInstance;
+
+    private final Object mLock = new Object();
+
+    private IAccessibilityManager mService;
+
+    final int mUserId;
+
+    final Handler mHandler;
+
+    final Handler.Callback mCallback;
+
+    boolean mIsEnabled;
+
+    int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+
+    boolean mIsTouchExplorationEnabled;
+
+    boolean mIsHighTextContrastEnabled;
+
+    AccessibilityPolicy mAccessibilityPolicy;
+
+    private final ArrayMap<AccessibilityStateChangeListener, Handler>
+            mAccessibilityStateChangeListeners = new ArrayMap<>();
+
+    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
+            mTouchExplorationStateChangeListeners = new ArrayMap<>();
+
+    private final ArrayMap<HighTextContrastChangeListener, Handler>
+            mHighTextContrastStateChangeListeners = new ArrayMap<>();
+
+    private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
+            mServicesStateChangeListeners = new ArrayMap<>();
+
+    /**
+     * Map from a view's accessibility id to the list of request preparers set for that view
+     */
+    private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
+
+    /**
+     * Listener for the system accessibility state. To listen for changes to the
+     * accessibility state on the device, implement this interface and register
+     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
      */
     public interface AccessibilityStateChangeListener {
 
         /**
-         * Called back on change in the accessibility state.
+         * Called when the accessibility enabled state changes.
          *
          * @param enabled Whether accessibility is enabled.
          */
-        public void onAccessibilityStateChanged(boolean enabled);
+        void onAccessibilityStateChanged(boolean enabled);
     }
 
     /**
@@ -73,7 +181,24 @@
          *
          * @param enabled Whether touch exploration is enabled.
          */
-        public void onTouchExplorationStateChanged(boolean enabled);
+        void onTouchExplorationStateChanged(boolean enabled);
+    }
+
+    /**
+     * Listener for changes to the state of accessibility services. Changes include services being
+     * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
+     * {@see #addAccessibilityServicesStateChangeListener}.
+     *
+     * @hide
+     */
+    public interface AccessibilityServicesStateChangeListener {
+
+        /**
+         * Called when the state of accessibility services changes.
+         *
+         * @param manager The manager that is calling back
+         */
+        void onAccessibilityServicesStateChanged(AccessibilityManager manager);
     }
 
     /**
@@ -81,6 +206,8 @@
      * the high text contrast state on the device, implement this interface and
      * register it with the system by calling
      * {@link #addHighTextContrastStateChangeListener}.
+     *
+     * @hide
      */
     public interface HighTextContrastChangeListener {
 
@@ -89,7 +216,7 @@
          *
          * @param enabled Whether high text contrast is enabled.
          */
-        public void onHighTextContrastStateChanged(boolean enabled);
+        void onHighTextContrastStateChanged(boolean enabled);
     }
 
     /**
@@ -148,21 +275,67 @@
 
     private final IAccessibilityManagerClient.Stub mClient =
             new IAccessibilityManagerClient.Stub() {
-                public void setState(int state) {
-                }
+        @Override
+        public void setState(int state) {
+            // We do not want to change this immediately as the application may
+            // have already checked that accessibility is on and fired an event,
+            // that is now propagating up the view tree, Hence, if accessibility
+            // is now off an exception will be thrown. We want to have the exception
+            // enforcement to guard against apps that fire unnecessary accessibility
+            // events when accessibility is off.
+            mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
+        }
 
-                public void notifyServicesStateChanged() {
+        @Override
+        public void notifyServicesStateChanged() {
+            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+            synchronized (mLock) {
+                if (mServicesStateChangeListeners.isEmpty()) {
+                    return;
                 }
+                listeners = new ArrayMap<>(mServicesStateChangeListeners);
+            }
 
-                public void setRelevantEventTypes(int eventTypes) {
-                }
-            };
+            int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                final AccessibilityServicesStateChangeListener listener =
+                        mServicesStateChangeListeners.keyAt(i);
+                mServicesStateChangeListeners.valueAt(i).post(() -> listener
+                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
+            }
+        }
+
+        @Override
+        public void setRelevantEventTypes(int eventTypes) {
+            mRelevantEventTypes = eventTypes;
+        }
+    };
 
     /**
      * Get an AccessibilityManager instance (create one if necessary).
      *
+     * @param context Context in which this manager operates.
+     *
+     * @hide
      */
     public static AccessibilityManager getInstance(Context context) {
+        synchronized (sInstanceSync) {
+            if (sInstance == null) {
+                final int userId;
+                if (Binder.getCallingUid() == Process.SYSTEM_UID
+                        || context.checkCallingOrSelfPermission(
+                                Manifest.permission.INTERACT_ACROSS_USERS)
+                                        == PackageManager.PERMISSION_GRANTED
+                        || context.checkCallingOrSelfPermission(
+                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                                        == PackageManager.PERMISSION_GRANTED) {
+                    userId = UserHandle.USER_CURRENT;
+                } else {
+                    userId = context.getUserId();
+                }
+                sInstance = new AccessibilityManager(context, null, userId);
+            }
+        }
         return sInstance;
     }
 
@@ -170,21 +343,65 @@
      * Create an instance.
      *
      * @param context A {@link Context}.
+     * @param service An interface to the backing service.
+     * @param userId User id under which to run.
+     *
+     * @hide
      */
     public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+        // Constructor can't be chained because we can't create an instance of an inner class
+        // before calling another constructor.
+        mCallback = new MyCallback();
+        mHandler = new Handler(context.getMainLooper(), mCallback);
+        mUserId = userId;
+        synchronized (mLock) {
+            tryConnectToServiceLocked(service);
+        }
     }
 
+    /**
+     * Create an instance.
+     *
+     * @param handler The handler to use
+     * @param service An interface to the backing service.
+     * @param userId User id under which to run.
+     *
+     * @hide
+     */
+    public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+        mCallback = new MyCallback();
+        mHandler = handler;
+        mUserId = userId;
+        synchronized (mLock) {
+            tryConnectToServiceLocked(service);
+        }
+    }
+
+    /**
+     * @hide
+     */
     public IAccessibilityManagerClient getClient() {
         return mClient;
     }
 
     /**
-     * Returns if the {@link AccessibilityManager} is enabled.
+     * @hide
+     */
+    @VisibleForTesting
+    public Handler.Callback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Returns if the accessibility in the system is enabled.
      *
-     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+     * @return True if accessibility is enabled, false otherwise.
      */
     public boolean isEnabled() {
-        return false;
+        synchronized (mLock) {
+            return mIsEnabled || (mAccessibilityPolicy != null
+                    && mAccessibilityPolicy.isEnabled(mIsEnabled));
+        }
     }
 
     /**
@@ -193,7 +410,13 @@
      * @return True if touch exploration is enabled, false otherwise.
      */
     public boolean isTouchExplorationEnabled() {
-        return true;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsTouchExplorationEnabled;
+        }
     }
 
     /**
@@ -203,47 +426,188 @@
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
+     * @return True if high text contrast is enabled, false otherwise.
+     *
+     * @hide
      */
     public boolean isHighTextContrastEnabled() {
-        return false;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsHighTextContrastEnabled;
+        }
     }
 
     /**
      * Sends an {@link AccessibilityEvent}.
+     *
+     * @param event The event to send.
+     *
+     * @throws IllegalStateException if accessibility is not enabled.
+     *
+     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+     * events is through calling
+     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+     * instead of this method to allow predecessors to augment/filter events sent by
+     * their descendants.
      */
     public void sendAccessibilityEvent(AccessibilityEvent event) {
+        final IAccessibilityManager service;
+        final int userId;
+        final AccessibilityEvent dispatchedEvent;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+            event.setEventTime(SystemClock.uptimeMillis());
+            if (mAccessibilityPolicy != null) {
+                dispatchedEvent = mAccessibilityPolicy.onAccessibilityEvent(event,
+                        mIsEnabled, mRelevantEventTypes);
+                if (dispatchedEvent == null) {
+                    return;
+                }
+            } else {
+                dispatchedEvent = event;
+            }
+            if (!isEnabled()) {
+                Looper myLooper = Looper.myLooper();
+                if (myLooper == Looper.getMainLooper()) {
+                    throw new IllegalStateException(
+                            "Accessibility off. Did you forget to check that?");
+                } else {
+                    // If we're not running on the thread with the main looper, it's possible for
+                    // the state of accessibility to change between checking isEnabled and
+                    // calling this method. So just log the error rather than throwing the
+                    // exception.
+                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+                    return;
+                }
+            }
+            if ((dispatchedEvent.getEventType() & mRelevantEventTypes) == 0) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + dispatchedEvent
+                            + " that is not among "
+                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
+                }
+                return;
+            }
+            userId = mUserId;
+        }
+        try {
+            // it is possible that this manager is in the same process as the service but
+            // client using it is called through Binder from another process. Example: MMS
+            // app adds a SMS notification and the NotificationManagerService calls this method
+            long identityToken = Binder.clearCallingIdentity();
+            try {
+                service.sendAccessibilityEvent(dispatchedEvent, userId);
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+            if (DEBUG) {
+                Log.i(LOG_TAG, dispatchedEvent + " sent");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error during sending " + dispatchedEvent + " ", re);
+        } finally {
+            if (event != dispatchedEvent) {
+                event.recycle();
+            }
+            dispatchedEvent.recycle();
+        }
     }
 
     /**
-     * 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.
+     * Requests feedback interruption from all accessibility services.
      */
     public void interrupt() {
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+            if (!isEnabled()) {
+                Looper myLooper = Looper.myLooper();
+                if (myLooper == Looper.getMainLooper()) {
+                    throw new IllegalStateException(
+                            "Accessibility off. Did you forget to check that?");
+                } else {
+                    // If we're not running on the thread with the main looper, it's possible for
+                    // the state of accessibility to change between checking isEnabled and
+                    // calling this method. So just log the error rather than throwing the
+                    // exception.
+                    Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
+                    return;
+                }
+            }
+            userId = mUserId;
+        }
+        try {
+            service.interrupt(userId);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Requested interrupt from all services");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
+        }
     }
 
     /**
      * Returns the {@link ServiceInfo}s of the installed accessibility services.
      *
      * @return An unmodifiable list with {@link ServiceInfo}s.
+     *
+     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
      */
     @Deprecated
     public List<ServiceInfo> getAccessibilityServiceList() {
-        return Collections.emptyList();
+        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
+        List<ServiceInfo> services = new ArrayList<>();
+        final int infoCount = infos.size();
+        for (int i = 0; i < infoCount; i++) {
+            AccessibilityServiceInfo info = infos.get(i);
+            services.add(info.getResolveInfo().serviceInfo);
+        }
+        return Collections.unmodifiableList(services);
     }
 
+    /**
+     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+     *
+     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+     */
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
-        return Collections.emptyList();
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return Collections.emptyList();
+            }
+            userId = mUserId;
+        }
+
+        List<AccessibilityServiceInfo> services = null;
+        try {
+            services = service.getInstalledAccessibilityServiceList(userId);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+        }
+        if (mAccessibilityPolicy != null) {
+            services = mAccessibilityPolicy.getInstalledAccessibilityServiceList(services);
+        }
+        if (services != null) {
+            return Collections.unmodifiableList(services);
+        } else {
+            return Collections.emptyList();
+        }
     }
 
     /**
@@ -258,21 +622,52 @@
      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
      */
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
             int feedbackTypeFlags) {
-        return Collections.emptyList();
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return Collections.emptyList();
+            }
+            userId = mUserId;
+        }
+
+        List<AccessibilityServiceInfo> services = null;
+        try {
+            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+        }
+        if (mAccessibilityPolicy != null) {
+            services = mAccessibilityPolicy.getEnabledAccessibilityServiceList(
+                    feedbackTypeFlags, services);
+        }
+        if (services != null) {
+            return Collections.unmodifiableList(services);
+        } else {
+            return Collections.emptyList();
+        }
     }
 
     /**
      * Registers an {@link AccessibilityStateChangeListener} for changes in
-     * the global accessibility state of the system.
+     * the global accessibility state of the system. Equivalent to calling
+     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addAccessibilityStateChangeListener(
-            AccessibilityStateChangeListener listener) {
+            @NonNull AccessibilityStateChangeListener listener) {
+        addAccessibilityStateChangeListener(listener, null);
         return true;
     }
 
@@ -286,22 +681,40 @@
      *                for a callback on the process's main handler.
      */
     public void addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mAccessibilityStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
+    /**
+     * Unregisters an {@link AccessibilityStateChangeListener}.
+     *
+     * @param listener The listener.
+     * @return True if the listener was previously registered.
+     */
     public boolean removeAccessibilityStateChangeListener(
-            AccessibilityStateChangeListener listener) {
-        return true;
+            @NonNull AccessibilityStateChangeListener listener) {
+        synchronized (mLock) {
+            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+            mAccessibilityStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
     }
 
     /**
      * Registers a {@link TouchExplorationStateChangeListener} for changes in
-     * the global touch exploration state of the system.
+     * the global touch exploration state of the system. Equivalent to calling
+     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
+        addTouchExplorationStateChangeListener(listener, null);
         return true;
     }
 
@@ -315,17 +728,104 @@
      *                for a callback on the process's main handler.
      */
     public void addTouchExplorationStateChangeListener(
-            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mTouchExplorationStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
     /**
      * Unregisters a {@link TouchExplorationStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
+     * @return True if listener was previously registered.
      */
     public boolean removeTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        return true;
+        synchronized (mLock) {
+            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
+            mTouchExplorationStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
+    }
+
+    /**
+     * Registers a {@link AccessibilityServicesStateChangeListener}.
+     *
+     * @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.
+     * @hide
+     */
+    public void addAccessibilityServicesStateChangeListener(
+            @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mServicesStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
+
+    /**
+     * Unregisters a {@link AccessibilityServicesStateChangeListener}.
+     *
+     * @param listener The listener.
+     *
+     * @hide
+     */
+    public void removeAccessibilityServicesStateChangeListener(
+            @NonNull AccessibilityServicesStateChangeListener listener) {
+        synchronized (mLock) {
+            mServicesStateChangeListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Registers a {@link AccessibilityRequestPreparer}.
+     */
+    public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+        if (mRequestPreparerLists == null) {
+            mRequestPreparerLists = new SparseArray<>(1);
+        }
+        int id = preparer.getView().getAccessibilityViewId();
+        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
+        if (requestPreparerList == null) {
+            requestPreparerList = new ArrayList<>(1);
+            mRequestPreparerLists.put(id, requestPreparerList);
+        }
+        requestPreparerList.add(preparer);
+    }
+
+    /**
+     * Unregisters a {@link AccessibilityRequestPreparer}.
+     */
+    public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+        if (mRequestPreparerLists == null) {
+            return;
+        }
+        int viewId = preparer.getView().getAccessibilityViewId();
+        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
+        if (requestPreparerList != null) {
+            requestPreparerList.remove(preparer);
+            if (requestPreparerList.isEmpty()) {
+                mRequestPreparerLists.remove(viewId);
+            }
+        }
+    }
+
+    /**
+     * Get the preparers that are registered for an accessibility ID
+     *
+     * @param id The ID of interest
+     * @return The list of preparers, or {@code null} if there are none.
+     *
+     * @hide
+     */
+    public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
+        if (mRequestPreparerLists == null) {
+            return null;
+        }
+        return mRequestPreparerLists.get(id);
     }
 
     /**
@@ -337,7 +837,12 @@
      * @hide
      */
     public void addHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
     /**
      * Unregisters a {@link HighTextContrastChangeListener}.
@@ -347,7 +852,64 @@
      * @hide
      */
     public void removeHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener) {}
+            @NonNull HighTextContrastChangeListener listener) {
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Sets the {@link AccessibilityPolicy} controlling this manager.
+     *
+     * @param policy The policy.
+     *
+     * @hide
+     */
+    public void setAccessibilityPolicy(@Nullable AccessibilityPolicy policy) {
+        synchronized (mLock) {
+            mAccessibilityPolicy = policy;
+        }
+    }
+
+    /**
+     * Check if the accessibility volume stream is active.
+     *
+     * @return True if accessibility volume is active (i.e. some service has requested it). False
+     * otherwise.
+     * @hide
+     */
+    public boolean isAccessibilityVolumeStreamActive() {
+        List<AccessibilityServiceInfo> serviceInfos =
+                getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        for (int i = 0; i < serviceInfos.size(); i++) {
+            if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Report a fingerprint gesture to accessibility. Only available for the system process.
+     *
+     * @param keyCode The key code of the gesture
+     * @return {@code true} if accessibility consumes the event. {@code false} if not.
+     * @hide
+     */
+    public boolean sendFingerprintGesture(int keyCode) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+        try {
+            return service.sendFingerprintGesture(keyCode);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
 
     /**
      * Sets the current state and notifies listeners, if necessary.
@@ -355,14 +917,312 @@
      * @param stateFlags The state flags.
      */
     private void setStateLocked(int stateFlags) {
+        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
+        final boolean touchExplorationEnabled =
+                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
+        final boolean highTextContrastEnabled =
+                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
+
+        final boolean wasEnabled = isEnabled();
+        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
+        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+
+        // Ensure listeners get current state from isZzzEnabled() calls.
+        mIsEnabled = enabled;
+        mIsTouchExplorationEnabled = touchExplorationEnabled;
+        mIsHighTextContrastEnabled = highTextContrastEnabled;
+
+        if (wasEnabled != isEnabled()) {
+            notifyAccessibilityStateChanged();
+        }
+
+        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+            notifyTouchExplorationStateChanged();
+        }
+
+        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
+            notifyHighTextContrastStateChanged();
+        }
     }
 
+    /**
+     * Find an installed service with the specified {@link ComponentName}.
+     *
+     * @param componentName The name to match to the service.
+     *
+     * @return The info corresponding to the installed service, or {@code null} if no such service
+     * is installed.
+     * @hide
+     */
+    public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
+            ComponentName componentName) {
+        final List<AccessibilityServiceInfo> installedServiceInfos =
+                getInstalledAccessibilityServiceList();
+        if ((installedServiceInfos == null) || (componentName == null)) {
+            return null;
+        }
+        for (int i = 0; i < installedServiceInfos.size(); i++) {
+            if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
+                return installedServiceInfos.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds an accessibility interaction connection interface for a given window.
+     * @param windowToken The window token to which a connection is added.
+     * @param connection The connection.
+     *
+     * @hide
+     */
     public int addAccessibilityInteractionConnection(IWindow windowToken,
-            IAccessibilityInteractionConnection connection) {
+            String packageName, IAccessibilityInteractionConnection connection) {
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return View.NO_ID;
+            }
+            userId = mUserId;
+        }
+        try {
+            return service.addAccessibilityInteractionConnection(windowToken, connection,
+                    packageName, userId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+        }
         return View.NO_ID;
     }
 
+    /**
+     * Removed an accessibility interaction connection interface for a given window.
+     * @param windowToken The window token to which a connection is removed.
+     *
+     * @hide
+     */
     public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.removeAccessibilityInteractionConnection(windowToken);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+        }
     }
 
+    /**
+     * Perform the accessibility shortcut if the caller has permission.
+     *
+     * @hide
+     */
+    public void performAccessibilityShortcut() {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.performAccessibilityShortcut();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
+        }
+    }
+
+    /**
+     * Notifies that the accessibility button in the system's navigation area has been clicked
+     *
+     * @hide
+     */
+    public void notifyAccessibilityButtonClicked() {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonClicked();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+        }
+    }
+
+    /**
+     * Notifies that the visibility of the accessibility button in the system's navigation area
+     * has changed.
+     *
+     * @param shown {@code true} if the accessibility button is visible within the system
+     *                  navigation area, {@code false} otherwise
+     * @hide
+     */
+    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonVisibilityChanged(shown);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
+        }
+    }
+
+    /**
+     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
+     * window. Intended for use by the System UI only.
+     *
+     * @param connection The connection to handle the actions. Set to {@code null} to avoid
+     * affecting the actions.
+     *
+     * @hide
+     */
+    public void setPictureInPictureActionReplacingConnection(
+            @Nullable IAccessibilityInteractionConnection connection) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.setPictureInPictureActionReplacingConnection(connection);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
+        }
+    }
+
+    private IAccessibilityManager getServiceLocked() {
+        if (mService == null) {
+            tryConnectToServiceLocked(null);
+        }
+        return mService;
+    }
+
+    private void tryConnectToServiceLocked(IAccessibilityManager service) {
+        if (service == null) {
+            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+            if (iBinder == null) {
+                return;
+            }
+            service = IAccessibilityManager.Stub.asInterface(iBinder);
+        }
+
+        try {
+            final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
+            setStateLocked(IntPair.first(userStateAndRelevantEvents));
+            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
+            mService = service;
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+        }
+    }
+
+    /**
+     * Notifies the registered {@link AccessibilityStateChangeListener}s.
+     */
+    private void notifyAccessibilityStateChanged() {
+        final boolean isEnabled;
+        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
+        synchronized (mLock) {
+            if (mAccessibilityStateChangeListeners.isEmpty()) {
+                return;
+            }
+            isEnabled = isEnabled();
+            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
+        }
+
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final AccessibilityStateChangeListener listener = listeners.keyAt(i);
+            listeners.valueAt(i).post(() ->
+                    listener.onAccessibilityStateChanged(isEnabled));
+        }
+    }
+
+    /**
+     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+     */
+    private void notifyTouchExplorationStateChanged() {
+        final boolean isTouchExplorationEnabled;
+        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
+        synchronized (mLock) {
+            if (mTouchExplorationStateChangeListeners.isEmpty()) {
+                return;
+            }
+            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
+        }
+
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final TouchExplorationStateChangeListener listener = listeners.keyAt(i);
+            listeners.valueAt(i).post(() ->
+                    listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
+        }
+    }
+
+    /**
+     * Notifies the registered {@link HighTextContrastChangeListener}s.
+     */
+    private void notifyHighTextContrastStateChanged() {
+        final boolean isHighTextContrastEnabled;
+        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+        synchronized (mLock) {
+            if (mHighTextContrastStateChangeListeners.isEmpty()) {
+                return;
+            }
+            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
+            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+        }
+
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final HighTextContrastChangeListener listener = listeners.keyAt(i);
+            listeners.valueAt(i).post(() ->
+                    listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+        }
+    }
+
+    /**
+     * Determines if the accessibility button within the system navigation area is supported.
+     *
+     * @return {@code true} if the accessibility button is supported on this device,
+     * {@code false} otherwise
+     */
+    public static boolean isAccessibilityButtonSupported() {
+        final Resources res = Resources.getSystem();
+        return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+    }
+
+    private final class MyCallback implements Handler.Callback {
+        public static final int MSG_SET_STATE = 1;
+
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_SET_STATE: {
+                    // See comment at mClient
+                    final int state = message.arg1;
+                    synchronized (mLock) {
+                        setStateLocked(state);
+                    }
+                } break;
+            }
+            return true;
+        }
+    }
 }
diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java
index 4c437dd..03f1c12 100644
--- a/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3874,6 +3874,24 @@
                         | 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.
diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java
index 1da998d..a6495d1 100644
--- a/android/view/autofill/AutofillPopupWindow.java
+++ b/android/view/autofill/AutofillPopupWindow.java
@@ -79,6 +79,11 @@
     public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
         mWindowPresenter = new WindowPresenter(presenter);
 
+        // 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.
+        setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
         setTouchModal(false);
         setOutsideTouchable(true);
         setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
diff --git a/android/view/inputmethod/BaseInputConnection.java b/android/view/inputmethod/BaseInputConnection.java
index 5f7a0f7..090e19f 100644
--- a/android/view/inputmethod/BaseInputConnection.java
+++ b/android/view/inputmethod/BaseInputConnection.java
@@ -522,7 +522,7 @@
             b = tmp;
         }
 
-        if (a == b) return null;
+        if (a == b || a < 0) return null;
 
         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
             return content.subSequence(a, b);
diff --git a/android/view/textclassifier/GenerateLinksLogger.java b/android/view/textclassifier/GenerateLinksLogger.java
index 73cf43b..067513f 100644
--- a/android/view/textclassifier/GenerateLinksLogger.java
+++ b/android/view/textclassifier/GenerateLinksLogger.java
@@ -19,13 +19,13 @@
 import android.annotation.Nullable;
 import android.metrics.LogMaker;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.Preconditions;
 
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
@@ -39,6 +39,7 @@
 public final class GenerateLinksLogger {
 
     private static final String LOG_TAG = "GenerateLinksLogger";
+    private static final boolean DEBUG_LOG_ENABLED = false;
     private static final String ZERO = "0";
 
     private final MetricsLogger mMetricsLogger;
@@ -127,7 +128,7 @@
     }
 
     private static void debugLog(LogMaker log) {
-        if (!Logger.DEBUG_LOG_ENABLED) return;
+        if (!DEBUG_LOG_ENABLED) return;
 
         final String callId = Objects.toString(
                 log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), "");
@@ -142,8 +143,9 @@
         final int latencyMs = Integer.parseInt(
                 Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO));
 
-        Log.d(LOG_TAG, String.format("%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
-                numLinks, linkLength, textLength, latencyMs, log.getPackageName()));
+        Log.d(LOG_TAG,
+                String.format(Locale.US, "%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
+                        numLinks, linkLength, textLength, latencyMs, log.getPackageName()));
     }
 
     /** Helper class for storing per-entity type statistics. */
diff --git a/android/view/textclassifier/Logger.java b/android/view/textclassifier/Logger.java
deleted file mode 100644
index f03906a..0000000
--- a/android/view/textclassifier/Logger.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * 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.content.Context;
-
-import com.android.internal.util.Preconditions;
-
-import java.text.BreakIterator;
-import java.util.Locale;
-import java.util.Objects;
-
-/**
- * A helper for logging TextClassifier related events.
- * @hide
- */
-public abstract class Logger {
-
-    private static final String LOG_TAG = "Logger";
-    /* package */ static final boolean DEBUG_LOG_ENABLED = true;
-
-    private @SelectionEvent.InvocationMethod int mInvocationMethod;
-    private SelectionEvent mPrevEvent;
-    private SelectionEvent mSmartEvent;
-    private SelectionEvent mStartEvent;
-
-    /**
-     * Logger that does not log anything.
-     * @hide
-     */
-    public static final Logger DISABLED = new Logger() {
-        @Override
-        public void writeEvent(SelectionEvent event) {}
-    };
-
-    @Nullable
-    private final Config mConfig;
-
-    public Logger(Config config) {
-        mConfig = Preconditions.checkNotNull(config);
-    }
-
-    private Logger() {
-        mConfig = null;
-    }
-
-    /**
-     * Writes the selection event to a log.
-     */
-    public abstract void writeEvent(@NonNull SelectionEvent event);
-
-    /**
-     * Returns true if the resultId matches that of a smart selection event (i.e.
-     * {@link SelectionEvent#EVENT_SMART_SELECTION_SINGLE} or
-     * {@link SelectionEvent#EVENT_SMART_SELECTION_MULTI}).
-     * Returns false otherwise.
-     */
-    public boolean isSmartSelection(@NonNull String resultId) {
-        return false;
-    }
-
-    /**
-     * Returns a token iterator for tokenizing text for logging purposes.
-     */
-    public BreakIterator getTokenIterator(@NonNull Locale locale) {
-        return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
-    }
-
-    /**
-     * Logs a "selection started" event.
-     *
-     * @param invocationMethod  the way the selection was triggered
-     * @param start  the token index of the selected token
-     */
-    public final void logSelectionStartedEvent(
-            @SelectionEvent.InvocationMethod int invocationMethod, int start) {
-        if (mConfig == null) {
-            return;
-        }
-
-        mInvocationMethod = invocationMethod;
-        logEvent(new SelectionEvent(
-                start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
-                TextClassifier.TYPE_UNKNOWN, mInvocationMethod, null, mConfig));
-    }
-
-    /**
-     * Logs a "selection modified" event.
-     * Use when the user modifies the selection.
-     *
-     * @param start  the start token (inclusive) index of the selection
-     * @param end  the end token (exclusive) index of the selection
-     */
-    public final void logSelectionModifiedEvent(int start, int end) {
-        Preconditions.checkArgument(end >= start, "end cannot be less than start");
-
-        if (mConfig == null) {
-            return;
-        }
-
-        logEvent(new SelectionEvent(
-                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
-                TextClassifier.TYPE_UNKNOWN, mInvocationMethod, null, mConfig));
-    }
-
-    /**
-     * Logs a "selection modified" event.
-     * Use when the user modifies the selection and the selection's entity type is known.
-     *
-     * @param start  the start token (inclusive) index of the selection
-     * @param end  the end token (exclusive) index of the selection
-     * @param classification  the TextClassification object returned by the TextClassifier that
-     *      classified the selected text
-     */
-    public final void logSelectionModifiedEvent(
-            int start, int end, @NonNull TextClassification classification) {
-        Preconditions.checkArgument(end >= start, "end cannot be less than start");
-        Preconditions.checkNotNull(classification);
-
-        if (mConfig == null) {
-            return;
-        }
-
-        final String entityType = classification.getEntityCount() > 0
-                ? classification.getEntity(0)
-                : TextClassifier.TYPE_UNKNOWN;
-        logEvent(new SelectionEvent(
-                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
-                entityType, mInvocationMethod, classification.getId(), mConfig));
-    }
-
-    /**
-     * Logs a "selection modified" event.
-     * Use when a TextClassifier modifies the selection.
-     *
-     * @param start  the start token (inclusive) index of the selection
-     * @param end  the end token (exclusive) index of the selection
-     * @param selection  the TextSelection object returned by the TextClassifier for the
-     *      specified selection
-     */
-    public final void logSelectionModifiedEvent(
-            int start, int end, @NonNull TextSelection selection) {
-        Preconditions.checkArgument(end >= start, "end cannot be less than start");
-        Preconditions.checkNotNull(selection);
-
-        if (mConfig == null) {
-            return;
-        }
-
-        final int eventType;
-        if (isSmartSelection(selection.getId())) {
-            eventType = end - start > 1
-                    ? SelectionEvent.EVENT_SMART_SELECTION_MULTI
-                    : SelectionEvent.EVENT_SMART_SELECTION_SINGLE;
-
-        } else {
-            eventType = SelectionEvent.EVENT_AUTO_SELECTION;
-        }
-        final String entityType = selection.getEntityCount() > 0
-                ? selection.getEntity(0)
-                : TextClassifier.TYPE_UNKNOWN;
-        logEvent(new SelectionEvent(start, end, eventType, entityType, mInvocationMethod,
-                selection.getId(), mConfig));
-    }
-
-    /**
-     * Logs 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 token (inclusive) index of the selection
-     * @param end  the end token (exclusive) index of the selection
-     * @param actionType  the action that was performed on the selection
-     */
-    public final void logSelectionActionEvent(
-            int start, int end, @SelectionEvent.ActionType int actionType) {
-        Preconditions.checkArgument(end >= start, "end cannot be less than start");
-        checkActionType(actionType);
-
-        if (mConfig == null) {
-            return;
-        }
-
-        logEvent(new SelectionEvent(
-                start, end, actionType, TextClassifier.TYPE_UNKNOWN, mInvocationMethod,
-                null, mConfig));
-    }
-
-    /**
-     * Logs 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 token (inclusive) index of the selection
-     * @param end  the end token (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 actionType is not a valid SelectionEvent actionType
-     */
-    public final void logSelectionActionEvent(
-            int start, int end, @SelectionEvent.ActionType int actionType,
-            @NonNull TextClassification classification) {
-        Preconditions.checkArgument(end >= start, "end cannot be less than start");
-        Preconditions.checkNotNull(classification);
-        checkActionType(actionType);
-
-        if (mConfig == null) {
-            return;
-        }
-
-        final String entityType = classification.getEntityCount() > 0
-                ? classification.getEntity(0)
-                : TextClassifier.TYPE_UNKNOWN;
-        logEvent(new SelectionEvent(start, end, actionType, entityType, mInvocationMethod,
-                classification.getId(), mConfig));
-    }
-
-    private void logEvent(@NonNull SelectionEvent event) {
-        Preconditions.checkNotNull(event);
-
-        if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
-                && mStartEvent == null) {
-            if (DEBUG_LOG_ENABLED) {
-                Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
-            }
-            return;
-        }
-
-        final long now = System.currentTimeMillis();
-        switch (event.getEventType()) {
-            case SelectionEvent.EVENT_SELECTION_STARTED:
-                Preconditions.checkArgument(event.getAbsoluteEnd() == event.getAbsoluteStart() + 1);
-                event.setSessionId(startNewSession());
-                mStartEvent = event;
-                break;
-            case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:  // fall through
-            case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
-                mSmartEvent = event;
-                break;
-            case SelectionEvent.EVENT_SELECTION_MODIFIED:  // fall through
-            case SelectionEvent.EVENT_AUTO_SELECTION:
-                if (mPrevEvent != null
-                        && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart()
-                        && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) {
-                    // Selection did not change. Ignore event.
-                    return;
-                }
-                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);
-        }
-        writeEvent(event);
-        mPrevEvent = event;
-
-        if (event.isTerminal()) {
-            endSession();
-        }
-    }
-
-    private TextClassificationSessionId startNewSession() {
-        endSession();
-        return new TextClassificationSessionId();
-    }
-
-    private void endSession() {
-        mPrevEvent = null;
-        mSmartEvent = null;
-        mStartEvent = null;
-    }
-
-    /**
-     * @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
-                return;
-            default:
-                throw new IllegalArgumentException(
-                        String.format(Locale.US, "%d is not an eventType", eventType));
-        }
-    }
-
-
-    /**
-     * A Logger config.
-     */
-    public static final class Config {
-
-        private final String mPackageName;
-        private final String mWidgetType;
-        @Nullable private final String mWidgetVersion;
-
-        /**
-         * @param context Context of the widget the logger logs for
-         * @param widgetType a name for the widget being logged for. e.g.
-         *      {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
-         * @param widgetVersion a string version info for the widget the logger logs for
-         */
-        public Config(
-                @NonNull Context context,
-                @TextClassifier.WidgetType String widgetType,
-                @Nullable String widgetVersion) {
-            mPackageName = Preconditions.checkNotNull(context).getPackageName();
-            mWidgetType = widgetType;
-            mWidgetVersion = widgetVersion;
-        }
-
-        /**
-         * Returns the package name of the application the logger logs for.
-         */
-        public String getPackageName() {
-            return mPackageName;
-        }
-
-        /**
-         * Returns the name for the widget being logged for. e.g.
-         * {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}.
-         */
-        public String getWidgetType() {
-            return mWidgetType;
-        }
-
-        /**
-         * Returns string version info for the logger. This is specific to the text classifier.
-         */
-        @Nullable
-        public String getWidgetVersion() {
-            return mWidgetVersion;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mPackageName, mWidgetType, mWidgetVersion);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == this) {
-                return true;
-            }
-
-            if (!(obj instanceof Config)) {
-                return false;
-            }
-
-            final Config other = (Config) obj;
-            return Objects.equals(mPackageName, other.mPackageName)
-                    && Objects.equals(mWidgetType, other.mWidgetType)
-                    && Objects.equals(mWidgetVersion, other.mWidgetType);
-        }
-    }
-}
diff --git a/android/view/textclassifier/SelectionEvent.java b/android/view/textclassifier/SelectionEvent.java
index 1e978cc..b073596 100644
--- a/android/view/textclassifier/SelectionEvent.java
+++ b/android/view/textclassifier/SelectionEvent.java
@@ -150,20 +150,6 @@
         mInvocationMethod = invocationMethod;
     }
 
-    SelectionEvent(
-            int start, int end,
-            @EventType int eventType, @EntityType String entityType,
-            @InvocationMethod int invocationMethod, @Nullable String resultId,
-            Logger.Config config) {
-        this(start, end, eventType, entityType, invocationMethod, resultId);
-        Preconditions.checkNotNull(config);
-        setTextClassificationSessionContext(
-                new TextClassificationContext.Builder(
-                        config.getPackageName(), config.getWidgetType())
-                        .setWidgetVersion(config.getWidgetVersion())
-                        .build());
-    }
-
     private SelectionEvent(Parcel in) {
         mAbsoluteStart = in.readInt();
         mAbsoluteEnd = in.readInt();
@@ -362,6 +348,7 @@
             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(
@@ -667,4 +654,4 @@
             return new SelectionEvent[size];
         }
     };
-}
\ No newline at end of file
+}
diff --git a/android/view/textclassifier/DefaultLogger.java b/android/view/textclassifier/SelectionSessionLogger.java
similarity index 88%
rename from android/view/textclassifier/DefaultLogger.java
rename to android/view/textclassifier/SelectionSessionLogger.java
index 203ca56..f2fb63e 100644
--- a/android/view/textclassifier/DefaultLogger.java
+++ b/android/view/textclassifier/SelectionSessionLogger.java
@@ -17,28 +17,29 @@
 package android.view.textclassifier;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.metrics.LogMaker;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.Preconditions;
 
+import java.text.BreakIterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
- * Default Logger.
- * Used internally by TextClassifierImpl.
+ * A helper for logging selection session events.
  * @hide
  */
-public final class DefaultLogger extends Logger {
+public final class SelectionSessionLogger {
 
-    private static final String LOG_TAG = "DefaultLogger";
+    private static final String LOG_TAG = "SelectionSessionLogger";
+    private static final boolean DEBUG_LOG_ENABLED = false;
     static final String CLASSIFIER_ID = "androidtc";
 
     private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
@@ -59,23 +60,16 @@
 
     private final MetricsLogger mMetricsLogger;
 
-    public DefaultLogger(@NonNull Config config) {
-        super(config);
+    public SelectionSessionLogger() {
         mMetricsLogger = new MetricsLogger();
     }
 
     @VisibleForTesting
-    public DefaultLogger(@NonNull Config config, @NonNull MetricsLogger metricsLogger) {
-        super(config);
+    public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) {
         mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
     }
 
-    @Override
-    public boolean isSmartSelection(@NonNull String signature) {
-        return CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature));
-    }
-
-    @Override
+    /** Emits a selection event to the logs. */
     public void writeEvent(@NonNull SelectionEvent event) {
         Preconditions.checkNotNull(event);
         final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
@@ -93,7 +87,7 @@
                 .addTaggedData(SMART_END, event.getSmartEnd())
                 .addTaggedData(EVENT_START, event.getStart())
                 .addTaggedData(EVENT_END, event.getEnd())
-                .addTaggedData(SESSION_ID, event.getSessionId());
+                .addTaggedData(SESSION_ID, event.getSessionId().flattenToString());
         mMetricsLogger.write(log);
         debugLog(log);
     }
@@ -225,9 +219,17 @@
         final int eventEnd = Integer.parseInt(
                 Objects.toString(log.getTaggedData(EVENT_END), ZERO));
 
-        Log.d(LOG_TAG, String.format("%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
-                index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd, widget,
-                model));
+        Log.d(LOG_TAG,
+                String.format(Locale.US, "%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
+                        index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd,
+                        widget, model));
+    }
+
+    /**
+     * Returns a token iterator for tokenizing text for logging purposes.
+     */
+    public static BreakIterator getTokenIterator(@NonNull Locale locale) {
+        return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
     }
 
     /**
@@ -260,8 +262,10 @@
             return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash);
         }
 
-        static String getClassifierId(String signature) {
-            Preconditions.checkNotNull(signature);
+        static String getClassifierId(@Nullable String signature) {
+            if (signature == null) {
+                return "";
+            }
             final int end = signature.indexOf("|");
             if (end >= 0) {
                 return signature.substring(0, end);
@@ -269,8 +273,10 @@
             return "";
         }
 
-        static String getModelName(String signature) {
-            Preconditions.checkNotNull(signature);
+        static String getModelName(@Nullable String signature) {
+            if (signature == null) {
+                return "";
+            }
             final int start = signature.indexOf("|") + 1;
             final int end = signature.indexOf("|", start);
             if (start >= 1 && end >= start) {
@@ -279,8 +285,10 @@
             return "";
         }
 
-        static int getHash(String signature) {
-            Preconditions.checkNotNull(signature);
+        static int getHash(@Nullable String signature) {
+            if (signature == null) {
+                return 0;
+            }
             final int index1 = signature.indexOf("|");
             final int index2 = signature.indexOf("|", index1);
             if (index2 > 0) {
diff --git a/android/view/textclassifier/SystemTextClassifier.java b/android/view/textclassifier/SystemTextClassifier.java
index 45fd6bf..490c389 100644
--- a/android/view/textclassifier/SystemTextClassifier.java
+++ b/android/view/textclassifier/SystemTextClassifier.java
@@ -28,7 +28,6 @@
 import android.service.textclassifier.ITextLinksCallback;
 import android.service.textclassifier.ITextSelectionCallback;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.Preconditions;
@@ -49,13 +48,6 @@
     private final TextClassificationConstants mSettings;
     private final TextClassifier mFallback;
     private final String mPackageName;
-
-    private final Object mLoggerLock = new Object();
-    @GuardedBy("mLoggerLock")
-    private Logger.Config mLoggerConfig;
-    @GuardedBy("mLoggerLock")
-    private Logger mLogger;
-    @GuardedBy("mLoggerLock")
     private TextClassificationSessionId mSessionId;
 
     public SystemTextClassifier(Context context, TextClassificationConstants settings)
@@ -147,27 +139,6 @@
     }
 
     @Override
-    public Logger getLogger(@NonNull Logger.Config config) {
-        Preconditions.checkNotNull(config);
-        synchronized (mLoggerLock) {
-            if (mLogger == null || !config.equals(mLoggerConfig)) {
-                mLoggerConfig = config;
-                mLogger = new Logger(config) {
-                    @Override
-                    public void writeEvent(SelectionEvent event) {
-                        try {
-                            mManagerService.onSelectionEvent(mSessionId, event);
-                        } catch (RemoteException e) {
-                            Log.e(LOG_TAG, "Error reporting selection event.", e);
-                        }
-                    }
-                };
-            }
-        }
-        return mLogger;
-    }
-
-    @Override
     public void destroy() {
         try {
             if (mSessionId != null) {
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 37a5d9a..96016b4 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -375,13 +375,13 @@
      */
     public static final class Builder {
 
-        @NonNull private String mText;
         @NonNull private List<RemoteAction> mActions = new ArrayList<>();
         @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
-        @Nullable Drawable mLegacyIcon;
-        @Nullable String mLegacyLabel;
-        @Nullable Intent mLegacyIntent;
-        @Nullable OnClickListener mLegacyOnClickListener;
+        @Nullable private String mText;
+        @Nullable private Drawable mLegacyIcon;
+        @Nullable private String mLegacyLabel;
+        @Nullable private Intent mLegacyIntent;
+        @Nullable private OnClickListener mLegacyOnClickListener;
         @Nullable private String mId;
 
         /**
@@ -721,4 +721,67 @@
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
     }
+
+    // TODO: Remove once apps can build against the latest sdk.
+    /**
+     * Optional input parameters for generating TextClassification.
+     * @hide
+     */
+    public static final class Options {
+
+        @Nullable private final TextClassificationSessionId mSessionId;
+        @Nullable private final Request mRequest;
+        @Nullable private LocaleList mDefaultLocales;
+        @Nullable private ZonedDateTime mReferenceTime;
+
+        public Options() {
+            this(null, null);
+        }
+
+        private Options(
+                @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
+            mSessionId = sessionId;
+            mRequest = request;
+        }
+
+        /** Helper to create Options from a Request. */
+        public static Options from(TextClassificationSessionId sessionId, Request request) {
+            final Options options = new Options(sessionId, request);
+            options.setDefaultLocales(request.getDefaultLocales());
+            options.setReferenceTime(request.getReferenceTime());
+            return options;
+        }
+
+        /** @param defaultLocales ordered list of locale preferences. */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        /** @param referenceTime refrence time used for interpreting relatives dates */
+        public Options setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+            mReferenceTime = referenceTime;
+            return this;
+        }
+
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        @Nullable
+        public ZonedDateTime getReferenceTime() {
+            return mReferenceTime;
+        }
+
+        @Nullable
+        public Request getRequest() {
+            return mRequest;
+        }
+
+        @Nullable
+        public TextClassificationSessionId getSessionId() {
+            return mSessionId;
+        }
+    }
 }
diff --git a/android/view/textclassifier/TextClassificationSession.java b/android/view/textclassifier/TextClassificationSession.java
index e8e300a..4c64198 100644
--- a/android/view/textclassifier/TextClassificationSession.java
+++ b/android/view/textclassifier/TextClassificationSession.java
@@ -17,7 +17,6 @@
 package android.view.textclassifier;
 
 import android.annotation.WorkerThread;
-import android.view.textclassifier.DefaultLogger.SignatureParser;
 import android.view.textclassifier.SelectionEvent.InvocationMethod;
 
 import com.android.internal.util.Preconditions;
@@ -222,7 +221,8 @@
         }
 
         private static boolean isPlatformLocalTextClassifierSmartSelection(String signature) {
-            return DefaultLogger.CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature));
+            return SelectionSessionLogger.CLASSIFIER_ID.equals(
+                    SelectionSessionLogger.SignatureParser.getClassifierId(signature));
         }
     }
 }
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index 54261be..da47bcb 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -41,8 +41,9 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Interface for providing text classification related features.
@@ -208,6 +209,26 @@
         return suggestSelection(request);
     }
 
+    // TODO: Remove once apps can build against the latest sdk.
+    /** @hide */
+    default TextSelection suggestSelection(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int selectionStartIndex,
+            @IntRange(from = 0) int selectionEndIndex,
+            @Nullable TextSelection.Options options) {
+        if (options == null) {
+            return suggestSelection(new TextSelection.Request.Builder(
+                    text, selectionStartIndex, selectionEndIndex).build());
+        } else if (options.getRequest() != null) {
+            return suggestSelection(options.getRequest());
+        } else {
+            return suggestSelection(
+                    new TextSelection.Request.Builder(text, selectionStartIndex, selectionEndIndex)
+                            .setDefaultLocales(options.getDefaultLocales())
+                            .build());
+        }
+    }
+
     /**
      * Classifies the specified text and returns a {@link TextClassification} object that can be
      * used to generate a widget for handling the classified text.
@@ -267,6 +288,26 @@
         return classifyText(request);
     }
 
+    // TODO: Remove once apps can build against the latest sdk.
+    /** @hide */
+    default TextClassification classifyText(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int startIndex,
+            @IntRange(from = 0) int endIndex,
+            @Nullable TextClassification.Options options) {
+        if (options == null) {
+            return classifyText(
+                    new TextClassification.Request.Builder(text, startIndex, endIndex).build());
+        } else if (options.getRequest() != null) {
+            return classifyText(options.getRequest());
+        } else {
+            return classifyText(new TextClassification.Request.Builder(text, startIndex, endIndex)
+                    .setDefaultLocales(options.getDefaultLocales())
+                    .setReferenceTime(options.getReferenceTime())
+                    .build());
+        }
+    }
+
     /**
      * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
      * links information.
@@ -288,6 +329,22 @@
         return new TextLinks.Builder(request.getText().toString()).build();
     }
 
+    // TODO: Remove once apps can build against the latest sdk.
+    /** @hide */
+    default TextLinks generateLinks(
+            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+        if (options == null) {
+            return generateLinks(new TextLinks.Request.Builder(text).build());
+        } else if (options.getRequest() != null) {
+            return generateLinks(options.getRequest());
+        } else {
+            return generateLinks(new TextLinks.Request.Builder(text)
+                    .setDefaultLocales(options.getDefaultLocales())
+                    .setEntityConfig(options.getEntityConfig())
+                    .build());
+        }
+    }
+
     /**
      * Returns the maximal length of text that can be processed by generateLinks.
      *
@@ -302,18 +359,6 @@
     }
 
     /**
-     * Returns a helper for logging TextClassifier related events.
-     *
-     * @param config logger configuration
-     * @hide
-     */
-    @WorkerThread
-    default Logger getLogger(@NonNull Logger.Config config) {
-        Preconditions.checkNotNull(config);
-        return Logger.DISABLED;
-    }
-
-    /**
      * Reports a selection event.
      *
      * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
@@ -377,6 +422,12 @@
                     /* includedEntityTypes */null, /* excludedEntityTypes */ null);
         }
 
+        // TODO: Remove once apps can build against the latest sdk.
+        /** @hide */
+        public static EntityConfig create(@Nullable Collection<String> hints) {
+            return createWithHints(hints);
+        }
+
         /**
          * Creates an EntityConfig.
          *
@@ -406,6 +457,12 @@
                     /* includedEntityTypes */ entityTypes, /* excludedEntityTypes */ null);
         }
 
+        // TODO: Remove once apps can build against the latest sdk.
+        /** @hide */
+        public static EntityConfig createWithEntityList(@Nullable Collection<String> entityTypes) {
+            return createWithExplicitEntityList(entityTypes);
+        }
+
         /**
          * Returns a list of the final set of entities to find.
          *
@@ -413,21 +470,15 @@
          *
          * This method is intended for use by TextClassifier implementations.
          */
-        public List<String> resolveEntityListModifications(@NonNull Collection<String> entities) {
-            final ArrayList<String> finalList = new ArrayList<>();
+        public Collection<String> resolveEntityListModifications(
+                @NonNull Collection<String> entities) {
+            final Set<String> finalSet = new HashSet();
             if (mUseHints) {
-                for (String entity : entities) {
-                    if (!mExcludedEntityTypes.contains(entity)) {
-                        finalList.add(entity);
-                    }
-                }
+                finalSet.addAll(entities);
             }
-            for (String entity : mIncludedEntityTypes) {
-                if (!mExcludedEntityTypes.contains(entity) && !finalList.contains(entity)) {
-                    finalList.add(entity);
-                }
-            }
-            return finalList;
+            finalSet.addAll(mIncludedEntityTypes);
+            finalSet.removeAll(mExcludedEntityTypes);
+            return finalSet;
         }
 
         /**
@@ -508,7 +559,7 @@
             final String string = request.getText().toString();
             final TextLinks.Builder links = new TextLinks.Builder(string);
 
-            final List<String> entities = request.getEntityConfig()
+            final Collection<String> entities = request.getEntityConfig()
                     .resolveEntityListModifications(Collections.emptyList());
             if (entities.contains(TextClassifier.TYPE_URL)) {
                 addLinks(links, string, TextClassifier.TYPE_URL);
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 7e3748a..2213355 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -94,11 +94,7 @@
 
     private final Object mLoggerLock = new Object();
     @GuardedBy("mLoggerLock") // Do not access outside this lock.
-    private Logger.Config mLoggerConfig;
-    @GuardedBy("mLoggerLock") // Do not access outside this lock.
-    private Logger mLogger;
-    @GuardedBy("mLoggerLock") // Do not access outside this lock.
-    private Logger mLogger2;  // This is the new logger. Will replace mLogger.
+    private SelectionSessionLogger mSessionLogger;
 
     private final TextClassificationConstants mSettings;
 
@@ -283,28 +279,14 @@
         }
     }
 
-    /** @inheritDoc */
-    @Override
-    public Logger getLogger(@NonNull Logger.Config config) {
-        Preconditions.checkNotNull(config);
-        synchronized (mLoggerLock) {
-            if (mLogger == null || !config.equals(mLoggerConfig)) {
-                mLoggerConfig = config;
-                mLogger = new DefaultLogger(config);
-            }
-        }
-        return mLogger;
-    }
-
     @Override
     public void onSelectionEvent(SelectionEvent event) {
         Preconditions.checkNotNull(event);
         synchronized (mLoggerLock) {
-            if (mLogger2 == null) {
-                mLogger2 = new DefaultLogger(
-                        new Logger.Config(mContext, WIDGET_TYPE_UNKNOWN, null));
+            if (mSessionLogger == null) {
+                mSessionLogger = new SelectionSessionLogger();
             }
-            mLogger2.writeEvent(event);
+            mSessionLogger.writeEvent(event);
         }
     }
 
@@ -331,7 +313,7 @@
 
     private String createId(String text, int start, int end) {
         synchronized (mLock) {
-            return DefaultLogger.createId(text, start, end, mContext, mModel.getVersion(),
+            return SelectionSessionLogger.createId(text, start, end, mContext, mModel.getVersion(),
                     mModel.getSupportedLocales());
         }
     }
diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java
index 17c7b13..851b2c9 100644
--- a/android/view/textclassifier/TextLinks.java
+++ b/android/view/textclassifier/TextLinks.java
@@ -28,6 +28,8 @@
 import android.text.method.MovementMethod;
 import android.text.style.ClickableSpan;
 import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.text.util.Linkify.LinkifyMask;
 import android.view.View;
 import android.view.textclassifier.TextClassifier.EntityType;
 import android.widget.TextView;
@@ -337,7 +339,7 @@
 
         /**
          * @return The config representing the set of entities to look for
-         * @see #setEntityConfig(TextClassifier.EntityConfig)
+         * @see Builder#setEntityConfig(TextClassifier.EntityConfig)
          */
         @Nullable
         public TextClassifier.EntityConfig getEntityConfig() {
@@ -607,4 +609,124 @@
             return new TextLinks(mFullText, mLinks);
         }
     }
+
+    // TODO: Remove once apps can build against the latest sdk.
+    /**
+     * Optional input parameters for generating TextLinks.
+     * @hide
+     */
+    public static final class Options {
+
+        @Nullable private final TextClassificationSessionId mSessionId;
+        @Nullable private final Request mRequest;
+        @Nullable private LocaleList mDefaultLocales;
+        @Nullable private TextClassifier.EntityConfig mEntityConfig;
+        private boolean mLegacyFallback;
+
+        private @ApplyStrategy int mApplyStrategy;
+        private Function<TextLink, TextLinkSpan> mSpanFactory;
+
+        private String mCallingPackageName;
+
+        public Options() {
+            this(null, null);
+        }
+
+        private Options(
+                @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
+            mSessionId = sessionId;
+            mRequest = request;
+        }
+
+        /** Helper to create Options from a Request. */
+        public static Options from(TextClassificationSessionId sessionId, Request request) {
+            final Options options = new Options(sessionId, request);
+            options.setDefaultLocales(request.getDefaultLocales());
+            options.setEntityConfig(request.getEntityConfig());
+            return options;
+        }
+
+        /** Returns a new options object based on the specified link mask. */
+        public static Options 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 Options().setEntityConfig(
+                    TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
+        }
+
+        /** @param defaultLocales ordered list of locale preferences. */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        /** @param entityConfig definition of which entity types to look for. */
+        public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
+            mEntityConfig = entityConfig;
+            return this;
+        }
+
+        /** @param applyStrategy strategy to use when resolving conflicts. */
+        public Options setApplyStrategy(@ApplyStrategy int applyStrategy) {
+            checkValidApplyStrategy(applyStrategy);
+            mApplyStrategy = applyStrategy;
+            return this;
+        }
+
+        /** @param spanFactory factory for converting TextLink to TextLinkSpan. */
+        public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
+            mSpanFactory = spanFactory;
+            return this;
+        }
+
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        @Nullable
+        public TextClassifier.EntityConfig getEntityConfig() {
+            return mEntityConfig;
+        }
+
+        @ApplyStrategy
+        public int getApplyStrategy() {
+            return mApplyStrategy;
+        }
+
+        @Nullable
+        public Function<TextLink, TextLinkSpan> getSpanFactory() {
+            return mSpanFactory;
+        }
+
+        @Nullable
+        public Request getRequest() {
+            return mRequest;
+        }
+
+        @Nullable
+        public TextClassificationSessionId getSessionId() {
+            return mSessionId;
+        }
+
+        private static void checkValidApplyStrategy(int applyStrategy) {
+            if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) {
+                throw new IllegalArgumentException(
+                        "Invalid apply strategy. See TextLinks.ApplyStrategy for options.");
+            }
+        }
+    }
 }
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
index 939e717..17687c9 100644
--- a/android/view/textclassifier/TextSelection.java
+++ b/android/view/textclassifier/TextSelection.java
@@ -375,4 +375,56 @@
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
     }
+
+
+    // TODO: Remove once apps can build against the latest sdk.
+    /**
+     * Optional input parameters for generating TextSelection.
+     * @hide
+     */
+    public static final class Options {
+
+        @Nullable private final TextClassificationSessionId mSessionId;
+        @Nullable private final Request mRequest;
+        @Nullable private LocaleList mDefaultLocales;
+        private boolean mDarkLaunchAllowed;
+
+        public Options() {
+            this(null, null);
+        }
+
+        private Options(
+                @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
+            mSessionId = sessionId;
+            mRequest = request;
+        }
+
+        /** Helper to create Options from a Request. */
+        public static Options from(TextClassificationSessionId sessionId, Request request) {
+            final Options options = new Options(sessionId, request);
+            options.setDefaultLocales(request.getDefaultLocales());
+            return options;
+        }
+
+        /** @param defaultLocales ordered list of locale preferences. */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        @Nullable
+        public Request getRequest() {
+            return mRequest;
+        }
+
+        @Nullable
+        public TextClassificationSessionId getSessionId() {
+            return mSessionId;
+        }
+    }
 }
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index 8e1f218..21ec42b 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,58 +1,219 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS 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.SystemService;
+import android.content.Context;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
 
+import com.android.internal.textservice.ITextServicesManager;
+
 import java.util.Locale;
 
 /**
- * A stub class of TextServicesManager for Layout-Lib.
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services 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> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
  */
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
 public final class TextServicesManager {
-    private static final TextServicesManager sInstance = new TextServicesManager();
-    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+    private static final String TAG = TextServicesManager.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    /**
+     * A compile time switch to control per-profile spell checker, which is not yet ready.
+     * @hide
+     */
+    public static final boolean DISABLE_PER_PROFILE_SPELL_CHECKER = true;
+
+    private static TextServicesManager sInstance;
+
+    private final ITextServicesManager mService;
+
+    private TextServicesManager() throws ServiceNotFoundException {
+        mService = ITextServicesManager.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
+    }
 
     /**
      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
      * @hide
      */
     public static TextServicesManager getInstance() {
-        return sInstance;
+        synchronized (TextServicesManager.class) {
+            if (sInstance == null) {
+                try {
+                    sInstance = new TextServicesManager();
+                } catch (ServiceNotFoundException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+            return sInstance;
+        }
     }
 
+    /**
+     * Returns the language component of a given locale string.
+     */
+    private static String parseLanguageFromLocaleString(String locale) {
+        final int idx = locale.indexOf('_');
+        if (idx < 0) {
+            return locale;
+        } else {
+            return locale.substring(0, idx);
+        }
+    }
+
+    /**
+     * Get a spell checker session for the specified spell checker
+     * @param locale the locale for the spell checker. If {@code locale} is null and
+     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+     * the locale specified in Settings will be returned only when it is same as {@code locale}.
+     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+     * selected.
+     * @param listener a spell checker session lister for getting results from a spell checker.
+     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+     * languages in settings will be returned.
+     * @return the spell checker session of the spell checker
+     */
     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
-        return null;
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        if (!referToSpellCheckerLanguageSettings && locale == null) {
+            throw new IllegalArgumentException("Locale should not be null if you don't refer"
+                    + " settings.");
+        }
+
+        if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+            return null;
+        }
+
+        final SpellCheckerInfo sci;
+        try {
+            sci = mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            return null;
+        }
+        if (sci == null) {
+            return null;
+        }
+        SpellCheckerSubtype subtypeInUse = null;
+        if (referToSpellCheckerLanguageSettings) {
+            subtypeInUse = getCurrentSpellCheckerSubtype(true);
+            if (subtypeInUse == null) {
+                return null;
+            }
+            if (locale != null) {
+                final String subtypeLocale = subtypeInUse.getLocale();
+                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+                    return null;
+                }
+            }
+        } else {
+            final String localeStr = locale.toString();
+            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+                final String tempSubtypeLocale = subtype.getLocale();
+                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+                if (tempSubtypeLocale.equals(localeStr)) {
+                    subtypeInUse = subtype;
+                    break;
+                } else if (tempSubtypeLanguage.length() >= 2 &&
+                        locale.getLanguage().equals(tempSubtypeLanguage)) {
+                    subtypeInUse = subtype;
+                }
+            }
+        }
+        if (subtypeInUse == null) {
+            return null;
+        }
+        final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
+        try {
+            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+                    session.getTextServicesSessionListener(),
+                    session.getSpellCheckerSessionListener(), bundle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return session;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo[] getEnabledSpellCheckers() {
-        return EMPTY_SPELL_CHECKER_INFO;
+        try {
+            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+            if (DBG) {
+                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+            }
+            return retval;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo getCurrentSpellChecker() {
-        return null;
+        try {
+            // Passing null as a locale for ICS
+            return mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -60,13 +221,22 @@
      */
     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
             boolean allowImplicitlySelectedSubtype) {
-        return null;
+        try {
+            // Passing null as a locale until we support multiple enabled spell checker subtypes.
+            return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * @hide
      */
     public boolean isSpellCheckerEnabled() {
-        return false;
+        try {
+            return mService.isSpellCheckerEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 }