Merge "Set default throttle to 30m."
diff --git a/api/current.txt b/api/current.txt
index b59af05..7db661b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -118,6 +118,7 @@
     field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -5677,10 +5678,11 @@
   }
 
   public final class RemoteAction implements android.os.Parcelable {
-    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
+    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
     method public int describeContents();
     method public void dump(java.lang.String, java.io.PrintWriter);
+    method public android.app.PendingIntent getActionIntent();
     method public java.lang.CharSequence getContentDescription();
     method public android.graphics.drawable.Icon getIcon();
     method public java.lang.CharSequence getTitle();
@@ -5688,10 +5690,6 @@
     field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
   }
 
-  public static abstract interface RemoteAction.OnActionListener {
-    method public abstract void onAction(android.app.RemoteAction);
-  }
-
   public final class RemoteInput implements android.os.Parcelable {
     method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
     method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
@@ -44368,6 +44366,7 @@
     ctor public View(android.content.Context, android.util.AttributeSet, int);
     ctor public View(android.content.Context, android.util.AttributeSet, int, int);
     method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
+    method public void addExtraDataToAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public void addFocusables(java.util.ArrayList<android.view.View>, int);
     method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
     method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
@@ -45063,6 +45062,7 @@
 
   public static class View.AccessibilityDelegate {
     ctor public View.AccessibilityDelegate();
+    method public void addExtraDataToAccessibilityNodeInfo(android.view.View, android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(android.view.View);
     method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
@@ -46183,6 +46183,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList();
     method public deprecated int getActions();
+    method public java.util.List<java.lang.String> getAvailableExtraData();
     method public void getBoundsInParent(android.graphics.Rect);
     method public void getBoundsInScreen(android.graphics.Rect);
     method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
@@ -46239,11 +46240,13 @@
     method public boolean performAction(int, android.os.Bundle);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(java.lang.String, android.os.Bundle);
     method public deprecated void removeAction(int);
     method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
     method public boolean removeChild(android.view.View);
     method public boolean removeChild(android.view.View, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String>);
     method public void setBoundsInParent(android.graphics.Rect);
     method public void setBoundsInScreen(android.graphics.Rect);
     method public void setCanOpenPopup(boolean);
@@ -46326,6 +46329,9 @@
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
     field public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo> CREATOR;
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
@@ -46407,6 +46413,7 @@
 
   public abstract class AccessibilityNodeProvider {
     ctor public AccessibilityNodeProvider();
+    method public void addExtraDataToAccessibilityNodeInfo(int, android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 70a7a2d..f77f6bb 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -206,6 +206,7 @@
     field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final java.lang.String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
     field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
@@ -5873,10 +5874,11 @@
   }
 
   public final class RemoteAction implements android.os.Parcelable {
-    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
+    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
     method public int describeContents();
     method public void dump(java.lang.String, java.io.PrintWriter);
+    method public android.app.PendingIntent getActionIntent();
     method public java.lang.CharSequence getContentDescription();
     method public android.graphics.drawable.Icon getIcon();
     method public java.lang.CharSequence getTitle();
@@ -5884,10 +5886,6 @@
     field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
   }
 
-  public static abstract interface RemoteAction.OnActionListener {
-    method public abstract void onAction(android.app.RemoteAction);
-  }
-
   public final class RemoteInput implements android.os.Parcelable {
     method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
     method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
@@ -47775,6 +47773,7 @@
     ctor public View(android.content.Context, android.util.AttributeSet, int);
     ctor public View(android.content.Context, android.util.AttributeSet, int, int);
     method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
+    method public void addExtraDataToAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public void addFocusables(java.util.ArrayList<android.view.View>, int);
     method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
     method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
@@ -48470,6 +48469,7 @@
 
   public static class View.AccessibilityDelegate {
     ctor public View.AccessibilityDelegate();
+    method public void addExtraDataToAccessibilityNodeInfo(android.view.View, android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(android.view.View);
     method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
@@ -49593,6 +49593,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList();
     method public deprecated int getActions();
+    method public java.util.List<java.lang.String> getAvailableExtraData();
     method public void getBoundsInParent(android.graphics.Rect);
     method public void getBoundsInScreen(android.graphics.Rect);
     method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
@@ -49649,11 +49650,13 @@
     method public boolean performAction(int, android.os.Bundle);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(java.lang.String, android.os.Bundle);
     method public deprecated void removeAction(int);
     method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
     method public boolean removeChild(android.view.View);
     method public boolean removeChild(android.view.View, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String>);
     method public void setBoundsInParent(android.graphics.Rect);
     method public void setBoundsInScreen(android.graphics.Rect);
     method public void setCanOpenPopup(boolean);
@@ -49736,6 +49739,9 @@
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
     field public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo> CREATOR;
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
@@ -49817,6 +49823,7 @@
 
   public abstract class AccessibilityNodeProvider {
     ctor public AccessibilityNodeProvider();
+    method public void addExtraDataToAccessibilityNodeInfo(int, android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7597585..37da0d3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -118,6 +118,7 @@
     field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -5688,10 +5689,11 @@
   }
 
   public final class RemoteAction implements android.os.Parcelable {
-    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
+    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
     method public int describeContents();
     method public void dump(java.lang.String, java.io.PrintWriter);
+    method public android.app.PendingIntent getActionIntent();
     method public java.lang.CharSequence getContentDescription();
     method public android.graphics.drawable.Icon getIcon();
     method public java.lang.CharSequence getTitle();
@@ -5699,10 +5701,6 @@
     field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
   }
 
-  public static abstract interface RemoteAction.OnActionListener {
-    method public abstract void onAction(android.app.RemoteAction);
-  }
-
   public final class RemoteInput implements android.os.Parcelable {
     method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
     method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
@@ -44674,6 +44672,7 @@
     ctor public View(android.content.Context, android.util.AttributeSet, int);
     ctor public View(android.content.Context, android.util.AttributeSet, int, int);
     method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
+    method public void addExtraDataToAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public void addFocusables(java.util.ArrayList<android.view.View>, int);
     method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
     method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
@@ -45370,6 +45369,7 @@
 
   public static class View.AccessibilityDelegate {
     ctor public View.AccessibilityDelegate();
+    method public void addExtraDataToAccessibilityNodeInfo(android.view.View, android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(android.view.View);
     method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
@@ -46494,6 +46494,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList();
     method public deprecated int getActions();
+    method public java.util.List<java.lang.String> getAvailableExtraData();
     method public void getBoundsInParent(android.graphics.Rect);
     method public void getBoundsInScreen(android.graphics.Rect);
     method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
@@ -46550,11 +46551,13 @@
     method public boolean performAction(int, android.os.Bundle);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(java.lang.String, android.os.Bundle);
     method public deprecated void removeAction(int);
     method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
     method public boolean removeChild(android.view.View);
     method public boolean removeChild(android.view.View, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String>);
     method public void setBoundsInParent(android.graphics.Rect);
     method public void setBoundsInScreen(android.graphics.Rect);
     method public void setCanOpenPopup(boolean);
@@ -46638,6 +46641,9 @@
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
     field public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo> CREATOR;
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final java.lang.String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
@@ -46719,6 +46725,7 @@
 
   public abstract class AccessibilityNodeProvider {
     ctor public AccessibilityNodeProvider();
+    method public void addExtraDataToAccessibilityNodeInfo(int, android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5bd37220..7a1931718 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -37,7 +37,8 @@
 
     boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
         long accessibilityNodeId, int interactionId,
-        IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
+        IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+        in Bundle arguments);
 
     boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
         String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9db2b92..16989c0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1896,7 +1896,7 @@
                 pi.getResDir(),
                 pi.getSplitResDirs(),
                 pi.getOverlayDirs(),
-                pi.getSharedLibraries(),
+                pi.getApplicationInfo().sharedLibraryFiles,
                 displayId,
                 overrideConfig,
                 compatInfo,
@@ -2180,7 +2180,7 @@
                 packageInfo.getResDir(),
                 splitDirs,
                 packageInfo.getOverlayDirs(),
-                packageInfo.getSharedLibraries(),
+                packageInfo.getApplicationInfo().sharedLibraryFiles,
                 displayId,
                 overrideConfiguration,
                 compatInfo,
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 17f5edd..7ed96af 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -311,7 +311,7 @@
                 }
 
                 mResources = ResourcesManager.getInstance().getResources(null, mResDir,
-                        splitPaths, mOverlayDirs, mSharedLibraries,
+                        splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                         Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                         getClassLoader());
             }
@@ -923,10 +923,6 @@
         return mOverlayDirs;
     }
 
-    public String[] getSharedLibraries() {
-        return mSharedLibraries;
-    }
-
     public String getDataDir() {
         return mDataDir;
     }
@@ -958,7 +954,7 @@
             }
 
             mResources = ResourcesManager.getInstance().getResources(null, mResDir,
-                    splitPaths, mOverlayDirs, mSharedLibraries,
+                    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                     Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                     getClassLoader());
         }
diff --git a/core/java/android/app/RemoteAction.java b/core/java/android/app/RemoteAction.java
index a37680f..5958bc1 100644
--- a/core/java/android/app/RemoteAction.java
+++ b/core/java/android/app/RemoteAction.java
@@ -35,55 +35,30 @@
  */
 public final class RemoteAction implements Parcelable {
 
-    /**
-     * Interface definition for a callback to be invoked when an action is invoked.
-     */
-    public interface OnActionListener {
-        /**
-         * Called when the associated action is invoked.
-         *
-         * @param action The action that was invoked.
-         */
-        void onAction(RemoteAction action);
-    }
-
     private static final String TAG = "RemoteAction";
 
-    private static final int MESSAGE_ACTION_INVOKED = 1;
-
     private final Icon mIcon;
     private final CharSequence mTitle;
     private final CharSequence mContentDescription;
-    private OnActionListener mActionCallback;
-    private final Messenger mMessenger;
+    private final PendingIntent mActionIntent;
 
     RemoteAction(Parcel in) {
         mIcon = Icon.CREATOR.createFromParcel(in);
         mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mMessenger = in.readParcelable(Messenger.class.getClassLoader());
+        mActionIntent = PendingIntent.CREATOR.createFromParcel(in);
     }
 
     public RemoteAction(@NonNull Icon icon, @NonNull CharSequence title,
-            @NonNull CharSequence contentDescription, @NonNull OnActionListener callback) {
-        if (icon == null || title == null || contentDescription == null || callback == null) {
+            @NonNull CharSequence contentDescription, @NonNull PendingIntent intent) {
+        if (icon == null || title == null || contentDescription == null || intent == null) {
             throw new IllegalArgumentException("Expected icon, title, content description and " +
                     "action callback");
         }
         mIcon = icon;
         mTitle = title;
         mContentDescription = contentDescription;
-        mActionCallback = callback;
-        mMessenger = new Messenger(new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case MESSAGE_ACTION_INVOKED:
-                        mActionCallback.onAction(RemoteAction.this);
-                        break;
-                }
-            }
-        });
+        mActionIntent = intent;
     }
 
     /**
@@ -108,22 +83,15 @@
     }
 
     /**
-     * Sends a message that the action was invoked.
-     * @hide
+     * Return the action intent.
      */
-    public void sendActionInvoked() {
-        Message m = Message.obtain();
-        m.what = MESSAGE_ACTION_INVOKED;
-        try {
-            mMessenger.send(m);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not send action-invoked", e);
-        }
+    public @NonNull PendingIntent getActionIntent() {
+        return mActionIntent;
     }
 
     @Override
     public RemoteAction clone() {
-        return new RemoteAction(mIcon, mTitle, mContentDescription, mActionCallback);
+        return new RemoteAction(mIcon, mTitle, mContentDescription, mActionIntent);
     }
 
     @Override
@@ -136,7 +104,7 @@
         mIcon.writeToParcel(out, 0);
         TextUtils.writeToParcel(mTitle, out, flags);
         TextUtils.writeToParcel(mContentDescription, out, flags);
-        out.writeParcelable(mMessenger, flags);
+        mActionIntent.writeToParcel(out, flags);
     }
 
     public void dump(String prefix, PrintWriter pw) {
@@ -144,6 +112,7 @@
         pw.print("title=" + mTitle);
         pw.print(" contentDescription=" + mContentDescription);
         pw.print(" icon=" + mIcon);
+        pw.print(" action=" + mActionIntent.getIntent());
         pw.println();
     }
 
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 13ee48e..4ff4840 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -17,9 +17,11 @@
 package android.view;
 
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Binder;
 import android.os.Bundle;
@@ -29,7 +31,6 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
-import android.text.TextUtils;
 import android.text.style.AccessibilityClickableSpan;
 import android.text.style.ClickableSpan;
 import android.util.LongSparseArray;
@@ -93,6 +94,19 @@
         mPrefetcher = new AccessibilityNodePrefetcher();
     }
 
+    private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid) {
+        // If the interrogation is performed by the same thread as the main UI
+        // thread in this process, set the message as a static reference so
+        // after this call completes the same thread but in the interrogating
+        // client can handle the message to generate the result.
+        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
+            AccessibilityInteractionClient.getInstanceForThread(
+                    interrogatingTid).setSameThreadMessage(message);
+        } else {
+            mHandler.sendMessage(message);
+        }
+    }
+
     private boolean isShown(View view) {
         // The first two checks are made also made by isShown() which
         // however traverses the tree up to the parent to catch that.
@@ -106,7 +120,7 @@
     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
             long accessibilityNodeId, Region interactiveRegion, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
-            long interrogatingTid, MagnificationSpec spec) {
+            long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
         message.arg1 = flags;
@@ -118,18 +132,10 @@
         args.arg1 = callback;
         args.arg2 = spec;
         args.arg3 = interactiveRegion;
+        args.arg4 = arguments;
         message.obj = args;
 
-        // If the interrogation is performed by the same thread as the main UI
-        // thread in this process, set the message as a static reference so
-        // after this call completes the same thread but in the interrogating
-        // client can handle the message to generate the result.
-        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
-            AccessibilityInteractionClient.getInstanceForThread(
-                    interrogatingTid).setSameThreadMessage(message);
-        } else {
-            mHandler.sendMessage(message);
-        }
+        scheduleMessage(message, interrogatingPid, interrogatingTid);
     }
 
     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
@@ -143,6 +149,7 @@
             (IAccessibilityInteractionConnectionCallback) args.arg1;
         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
         final Region interactiveRegion = (Region) args.arg3;
+        final Bundle arguments = (Bundle) args.arg4;
 
         args.recycle();
 
@@ -160,29 +167,12 @@
                 root = findViewByAccessibilityId(accessibilityViewId);
             }
             if (root != null && isShown(root)) {
-                mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
+                mPrefetcher.prefetchAccessibilityNodeInfos(
+                        root, virtualDescendantId, flags, infos, arguments);
             }
         } finally {
-            try {
-                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
-                // Recycle if called from another process. Specs are cached in the
-                // system process and obtained from a pool when read from parcel.
-                if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) {
-                    spec.recycle();
-                }
-                adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
-                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
-                infos.clear();
-            } catch (RemoteException re) {
-                /* ignore - the other side will time out */
-            }
-
-            // Recycle if called from the same process. Regions are obtained in
-            // the system process and instantiated  when read from parcel.
-            if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) {
-                interactiveRegion.recycle();
-            }
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, callback, interactionId, spec, interactiveRegion);
         }
     }
 
@@ -201,19 +191,9 @@
         args.arg2 = spec;
         args.arg3 = viewId;
         args.arg4 = interactiveRegion;
-
         message.obj = args;
 
-        // If the interrogation is performed by the same thread as the main UI
-        // thread in this process, set the message as a static reference so
-        // after this call completes the same thread but in the interrogating
-        // client can handle the message to generate the result.
-        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
-            AccessibilityInteractionClient.getInstanceForThread(
-                    interrogatingTid).setSameThreadMessage(message);
-        } else {
-            mHandler.sendMessage(message);
-        }
+        scheduleMessage(message, interrogatingPid, interrogatingTid);
     }
 
     private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
@@ -227,7 +207,6 @@
         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
         final String viewId = (String) args.arg3;
         final Region interactiveRegion = (Region) args.arg4;
-
         args.recycle();
 
         final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
@@ -257,25 +236,8 @@
                 mAddNodeInfosForViewId.reset();
             }
         } finally {
-            try {
-                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
-                // Recycle if called from another process. Specs are cached in the
-                // system process and obtained from a pool when read from parcel.
-                if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) {
-                    spec.recycle();
-                }
-                adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
-                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
-            } catch (RemoteException re) {
-                /* ignore - the other side will time out */
-            }
-
-            // Recycle if called from the same process. Regions are obtained in
-            // the system process and instantiated  when read from parcel.
-            if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) {
-                interactiveRegion.recycle();
-            }
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, callback, interactionId, spec, interactiveRegion);
         }
     }
 
@@ -297,16 +259,7 @@
         args.arg4 = interactiveRegion;
         message.obj = args;
 
-        // If the interrogation is performed by the same thread as the main UI
-        // thread in this process, set the message as a static reference so
-        // after this call completes the same thread but in the interrogating
-        // client can handle the message to generate the result.
-        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
-            AccessibilityInteractionClient.getInstanceForThread(
-                    interrogatingTid).setSameThreadMessage(message);
-        } else {
-            mHandler.sendMessage(message);
-        }
+        scheduleMessage(message, interrogatingPid, interrogatingTid);
     }
 
     private void findAccessibilityNodeInfosByTextUiThread(Message message) {
@@ -375,31 +328,14 @@
                 }
             }
         } finally {
-            try {
-                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
-                // Recycle if called from another process. Specs are cached in the
-                // system process and obtained from a pool when read from parcel.
-                if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) {
-                    spec.recycle();
-                }
-                adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
-                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
-            } catch (RemoteException re) {
-                /* ignore - the other side will time out */
-            }
-
-            // Recycle if called from the same process. Regions are obtained in
-            // the system process and instantiated  when read from parcel.
-            if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) {
-                interactiveRegion.recycle();
-            }
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, callback, interactionId, spec, interactiveRegion);
         }
     }
 
     public void findFocusClientThread(long accessibilityNodeId, int focusType,
             Region interactiveRegion, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
             long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FIND_FOCUS;
@@ -416,16 +352,7 @@
 
         message.obj = args;
 
-        // If the interrogation is performed by the same thread as the main UI
-        // thread in this process, set the message as a static reference so
-        // after this call completes the same thread but in the interrogating
-        // client can handle the message to generate the result.
-        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
-            AccessibilityInteractionClient.getInstanceForThread(
-                    interrogatingTid).setSameThreadMessage(message);
-        } else {
-            mHandler.sendMessage(message);
-        }
+        scheduleMessage(message, interrogatingPid, interrogatingTid);
     }
 
     private void findFocusUiThread(Message message) {
@@ -497,31 +424,14 @@
                 }
             }
         } finally {
-            try {
-                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-                applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
-                // Recycle if called from another process. Specs are cached in the
-                // system process and obtained from a pool when read from parcel.
-                if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) {
-                    spec.recycle();
-                }
-                adjustIsVisibleToUserIfNeeded(focused, interactiveRegion);
-                callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
-            } catch (RemoteException re) {
-                /* ignore - the other side will time out */
-            }
-
-            // Recycle if called from the same process. Regions are obtained in
-            // the system process and instantiated  when read from parcel.
-            if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) {
-                interactiveRegion.recycle();
-            }
+            updateInfoForViewportAndReturnFindNodeResult(
+                    focused, callback, interactionId, spec, interactiveRegion);
         }
     }
 
     public void focusSearchClientThread(long accessibilityNodeId, int direction,
             Region interactiveRegion, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
             long interrogatingTid, MagnificationSpec spec) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
@@ -537,16 +447,7 @@
 
         message.obj = args;
 
-        // If the interrogation is performed by the same thread as the main UI
-        // thread in this process, set the message as a static reference so
-        // after this call completes the same thread but in the interrogating
-        // client can handle the message to generate the result.
-        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
-            AccessibilityInteractionClient.getInstanceForThread(
-                    interrogatingTid).setSameThreadMessage(message);
-        } else {
-            mHandler.sendMessage(message);
-        }
+        scheduleMessage(message, interrogatingPid, interrogatingTid);
     }
 
     private void focusSearchUiThread(Message message) {
@@ -582,31 +483,14 @@
                 }
             }
         } finally {
-            try {
-                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-                applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
-                // Recycle if called from another process. Specs are cached in the
-                // system process and obtained from a pool when read from parcel.
-                if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) {
-                    spec.recycle();
-                }
-                adjustIsVisibleToUserIfNeeded(next, interactiveRegion);
-                callback.setFindAccessibilityNodeInfoResult(next, interactionId);
-            } catch (RemoteException re) {
-                /* ignore - the other side will time out */
-            }
-
-            // Recycle if called from the same process. Regions are obtained in
-            // the system process and instantiated  when read from parcel.
-            if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) {
-                interactiveRegion.recycle();
-            }
+            updateInfoForViewportAndReturnFindNodeResult(
+                    next, callback, interactionId, spec, interactiveRegion);
         }
     }
 
     public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
             Bundle arguments, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
             long interrogatingTid) {
         Message message = mHandler.obtainMessage();
         message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
@@ -622,16 +506,7 @@
 
         message.obj = args;
 
-        // If the interrogation is performed by the same thread as the main UI
-        // thread in this process, set the message as a static reference so
-        // after this call completes the same thread but in the interrogating
-        // client can handle the message to generate the result.
-        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
-            AccessibilityInteractionClient.getInstanceForThread(
-                    interrogatingTid).setSameThreadMessage(message);
-        } else {
-            mHandler.sendMessage(message);
-        }
+        scheduleMessage(message, interrogatingPid, interrogatingTid);
     }
 
     private void performAccessibilityActionUiThread(Message message) {
@@ -742,26 +617,6 @@
         }
     }
 
-    private void applyAppScaleAndMagnificationSpecIfNeeded(Point point,
-            MagnificationSpec spec) {
-        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
-        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
-            return;
-        }
-
-        if (applicationScale != 1.0f) {
-            point.x *= applicationScale;
-            point.y *= applicationScale;
-        }
-
-        if (spec != null) {
-            point.x *= spec.scale;
-            point.y *= spec.scale;
-            point.x += (int) spec.offsetX;
-            point.y += (int) spec.offsetY;
-        }
-    }
-
     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
             MagnificationSpec spec) {
         if (info == null) {
@@ -791,6 +646,25 @@
         info.setBoundsInParent(boundsInParent);
         info.setBoundsInScreen(boundsInScreen);
 
+        // Scale text locations if they are present
+        if (info.hasExtras()) {
+            Bundle extras = info.getExtras();
+            Parcelable[] textLocations =
+                    extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+            if (textLocations != null) {
+                for (int i = 0; i < textLocations.length; i++) {
+                    // Unchecked cast - an app that puts other objects in this bundle with this
+                    // key will crash.
+                    RectF textLocation = ((RectF) textLocations[i]);
+                    textLocation.scale(applicationScale);
+                    if (spec != null) {
+                        textLocation.scale(spec.scale);
+                        textLocation.offset(spec.offsetX, spec.offsetY);
+                    }
+                }
+            }
+        }
+
         if (spec != null) {
             AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
             if (attachInfo.mDisplay == null) {
@@ -829,6 +703,53 @@
         return (appScale != 1.0f || (spec != null && !spec.isNop()));
     }
 
+    private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
+            IAccessibilityInteractionConnectionCallback callback, int interactionId,
+            MagnificationSpec spec, Region interactiveRegion) {
+        try {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+            adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
+            callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+            infos.clear();
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        } finally {
+            recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
+        }
+    }
+
+    private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
+            IAccessibilityInteractionConnectionCallback callback, int interactionId,
+            MagnificationSpec spec, Region interactiveRegion) {
+        try {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+            adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+            callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+        } catch (RemoteException re) {
+                /* ignore - the other side will time out */
+        } finally {
+            recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
+        }
+    }
+
+    private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) {
+        if (android.os.Process.myPid() != Binder.getCallingPid()) {
+            // Specs are cached in the system process and obtained from a pool when read from
+            // a parcel, so only recycle the spec if called from another process.
+            if (spec != null) {
+                spec.recycle();
+            }
+        } else {
+            // Regions are obtained in the system process and instantiated when read from
+            // a parcel, so only recycle the region if caled from the same process.
+            if (region != null) {
+                region.recycle();
+            }
+        }
+    }
+
     private boolean handleClickableSpanActionUiThread(
             View view, int virtualDescendantId, Bundle arguments) {
         Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
@@ -872,11 +793,18 @@
         private final ArrayList<View> mTempViewList = new ArrayList<View>();
 
         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
-                List<AccessibilityNodeInfo> outInfos) {
+                List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+            // Determine if we'll be populating extra data
+            final String extraDataRequested = (arguments == null) ? null
+                    : arguments.getString(AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY);
             if (provider == null) {
                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
                 if (root != null) {
+                    if (extraDataRequested != null) {
+                        view.addExtraDataToAccessibilityNodeInfo(
+                                root, extraDataRequested, arguments);
+                    }
                     outInfos.add(root);
                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
                         prefetchPredecessorsOfRealNode(view, outInfos);
@@ -889,14 +817,14 @@
                     }
                 }
             } else {
-                final AccessibilityNodeInfo root;
-                if (virtualViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
-                    root = provider.createAccessibilityNodeInfo(virtualViewId);
-                } else {
-                    root = provider.createAccessibilityNodeInfo(
-                            AccessibilityNodeProvider.HOST_VIEW_ID);
-                }
+                final int idForRoot = (virtualViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
+                        ? AccessibilityNodeProvider.HOST_VIEW_ID : virtualViewId;
+                final AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(idForRoot);
                 if (root != null) {
+                    if (extraDataRequested != null) {
+                        provider.addExtraDataToAccessibilityNodeInfo(
+                                idForRoot, root, extraDataRequested, arguments);
+                    }
                     outInfos.add(root);
                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
@@ -1202,12 +1130,12 @@
     }
 
     private class PrivateHandler extends Handler {
-        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
-        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
-        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
-        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
-        private final static int MSG_FIND_FOCUS = 5;
-        private final static int MSG_FOCUS_SEARCH = 6;
+        private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
+        private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
+        private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
+        private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
+        private static final int MSG_FIND_FOCUS = 5;
+        private static final int MSG_FOCUS_SEARCH = 6;
 
         public PrivateHandler(Looper looper) {
             super(looper);
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 41a13cf..7fde8a6 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -52,7 +52,9 @@
     final Rect mFocusedRect = new Rect();
     final Rect mOtherRect = new Rect();
     final Rect mBestCandidateRect = new Rect();
-    final SequentialFocusComparator mSequentialFocusComparator = new SequentialFocusComparator();
+    private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator =
+            new UserSpecifiedFocusComparator();
+    private final FocusComparator mFocusComparator = new FocusComparator();
 
     private final ArrayList<View> mTempList = new ArrayList<View>();
 
@@ -225,12 +227,10 @@
             View focused, Rect focusedRect, int direction) {
         try {
             // Note: This sort is stable.
-            mSequentialFocusComparator.setRoot(root);
-            mSequentialFocusComparator.setIsLayoutRtl(root.isLayoutRtl());
-            mSequentialFocusComparator.setFocusables(focusables);
-            Collections.sort(focusables, mSequentialFocusComparator);
+            mUserSpecifiedFocusComparator.setFocusables(focusables);
+            Collections.sort(focusables, mUserSpecifiedFocusComparator);
         } finally {
-            mSequentialFocusComparator.recycle();
+            mUserSpecifiedFocusComparator.recycle();
         }
 
         final int count = focusables.size();
@@ -703,36 +703,80 @@
         return id != 0 && id != View.NO_ID;
     }
 
-    /**
-     * Sorts views according to their visual layout and geometry for default tab order.
-     * If views are part of a focus chain (nextFocusForwardId), then they are all grouped
-     * together. The head of the chain is used to determine the order of the chain and is
-     * first in the order and the tail of the chain is the last in the order. The views
-     * in the middle of the chain can be arbitrary order.
-     * This is used for sequential focus traversal.
-     */
-    private static final class SequentialFocusComparator implements Comparator<View> {
+    static FocusComparator getFocusComparator(ViewGroup root, boolean isRtl) {
+        FocusComparator comparator = getInstance().mFocusComparator;
+        comparator.setRoot(root);
+        comparator.setIsLayoutRtl(isRtl);
+        return comparator;
+    }
+
+    static final class FocusComparator implements Comparator<View> {
         private final Rect mFirstRect = new Rect();
         private final Rect mSecondRect = new Rect();
-        private ViewGroup mRoot;
+        private ViewGroup mRoot = null;
         private boolean mIsLayoutRtl;
-        private final SparseArray<View> mFocusables = new SparseArray<View>();
-        private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray();
-        private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>();
 
-        public void recycle() {
-            mRoot = null;
-            mFocusables.clear();
-            mHeadsOfChains.clear();
-            mIsConnectedTo.clear();
+        public void setIsLayoutRtl(boolean b) {
+            mIsLayoutRtl = b;
         }
 
         public void setRoot(ViewGroup root) {
             mRoot = root;
         }
 
-        public void setIsLayoutRtl(boolean b) {
-            mIsLayoutRtl = b;
+        public int compare(View first, View second) {
+            if (first == second) {
+                return 0;
+            }
+
+            getRect(first, mFirstRect);
+            getRect(second, mSecondRect);
+
+            if (mFirstRect.top < mSecondRect.top) {
+                return -1;
+            } else if (mFirstRect.top > mSecondRect.top) {
+                return 1;
+            } else if (mFirstRect.left < mSecondRect.left) {
+                return mIsLayoutRtl ? 1 : -1;
+            } else if (mFirstRect.left > mSecondRect.left) {
+                return mIsLayoutRtl ? -1 : 1;
+            } else if (mFirstRect.bottom < mSecondRect.bottom) {
+                return -1;
+            } else if (mFirstRect.bottom > mSecondRect.bottom) {
+                return 1;
+            } else if (mFirstRect.right < mSecondRect.right) {
+                return mIsLayoutRtl ? 1 : -1;
+            } else if (mFirstRect.right > mSecondRect.right) {
+                return mIsLayoutRtl ? -1 : 1;
+            } else {
+                // The view are distinct but completely coincident so we consider
+                // them equal for our purposes.  Since the sort is stable, this
+                // means that the views will retain their layout order relative to one another.
+                return 0;
+            }
+        }
+
+        private void getRect(View view, Rect rect) {
+            view.getDrawingRect(rect);
+            mRoot.offsetDescendantRectToMyCoords(view, rect);
+        }
+    }
+
+    /**
+     * Sorts views according to any explicitly-specified focus-chains. If there are no explicitly
+     * specified focus chains (eg. no nextFocusForward attributes defined), this should be a no-op.
+     */
+    private static final class UserSpecifiedFocusComparator implements Comparator<View> {
+        private final SparseArray<View> mFocusables = new SparseArray<View>();
+        private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray();
+        private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>();
+        private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>();
+
+        public void recycle() {
+            mFocusables.clear();
+            mHeadsOfChains.clear();
+            mIsConnectedTo.clear();
+            mOriginalOrdinal.clear();
         }
 
         public void setFocusables(ArrayList<View> focusables) {
@@ -755,6 +799,10 @@
                     setHeadOfChain(view);
                 }
             }
+
+            for (int i = 0; i < focusables.size(); ++i) {
+                mOriginalOrdinal.put(focusables.get(i), i);
+            }
         }
 
         private void setHeadOfChain(View head) {
@@ -793,44 +841,22 @@
                     return 1; // first is end of chain
                 }
             }
+            boolean involvesChain = false;
             if (firstHead != null) {
                 first = firstHead;
+                involvesChain = true;
             }
             if (secondHead != null) {
                 second = secondHead;
+                involvesChain = true;
             }
 
-            // First see if they belong to the same focus chain.
-            getRect(first, mFirstRect);
-            getRect(second, mSecondRect);
-
-            if (mFirstRect.top < mSecondRect.top) {
-                return -1;
-            } else if (mFirstRect.top > mSecondRect.top) {
-                return 1;
-            } else if (mFirstRect.left < mSecondRect.left) {
-                return mIsLayoutRtl ? 1 : -1;
-            } else if (mFirstRect.left > mSecondRect.left) {
-                return mIsLayoutRtl ? -1 : 1;
-            } else if (mFirstRect.bottom < mSecondRect.bottom) {
-                return -1;
-            } else if (mFirstRect.bottom > mSecondRect.bottom) {
-                return 1;
-            } else if (mFirstRect.right < mSecondRect.right) {
-                return mIsLayoutRtl ? 1 : -1;
-            } else if (mFirstRect.right > mSecondRect.right) {
-                return mIsLayoutRtl ? -1 : 1;
+            if (involvesChain) {
+                // keep original order between chains
+                return mOriginalOrdinal.get(first) < mOriginalOrdinal.get(second) ? -1 : 1;
             } else {
-                // The view are distinct but completely coincident so we consider
-                // them equal for our purposes.  Since the sort is stable, this
-                // means that the views will retain their layout order relative to one another.
                 return 0;
             }
         }
-
-        private void getRect(View view, Rect rect) {
-            view.getDrawingRect(rect);
-            mRoot.offsetDescendantRectToMyCoords(view, rect);
-        }
     }
 }
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index d59be02..2fe98c0 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -16,8 +16,6 @@
 
 package android.view;
 
-import android.graphics.Rect;
-
 /**
  * An interface to the PinnedStackController to update it of state changes, and to query
  * information based on the current state.
@@ -27,17 +25,7 @@
 interface IPinnedStackController {
 
     /**
-     * Notifies the controller that the user is currently interacting with the PIP.
-     */
-    oneway void setInInteractiveMode(boolean inInteractiveMode);
-
-    /**
-     * Notifies the controller that the PIP is currently minimized.
+     * Notifies the controller that the PiP is currently minimized.
      */
     oneway void setIsMinimized(boolean isMinimized);
-
-    /**
-     * Notifies the controller that the desired snap mode is to the closest edge.
-     */
-    oneway void setSnapToEdge(boolean snapToEdge);
 }
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 3c348c5..c7340bf 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.pm.ParceledListSlice;
+import android.graphics.Rect;
 import android.view.IPinnedStackController;
 
 /**
@@ -33,10 +34,22 @@
     void onListenerRegistered(IPinnedStackController controller);
 
     /**
-     * Called when window manager decides to adjust the pinned stack bounds, or when the listener
-     * is first registered to allow the listener to synchronized its state with the controller.
+     * Called when the window manager has detected a change that would cause the movement bounds
+     * to be changed (ie. after configuration change, aspect ratio change, etc). It then provides
+     * the components that allow the listener to calculate the movement bounds itself. The
+     * {@param normalBounds} are also the default bounds that the PiP would be entered in its
+     * current state with the aspect ratio applied.
      */
-    void onBoundsChanged(boolean adjustedForIme);
+    void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds,
+            boolean fromImeAdjustement);
+
+    /**
+     * Called when window manager decides to adjust the pinned stack bounds because of the IME, or
+     * when the listener is first registered to allow the listener to synchronized its state with
+     * the controller.  This call will always be followed by a onMovementBoundsChanged() call
+     * with fromImeAdjustement set to true.
+     */
+    void onImeVisibilityChanged(boolean imeVisible, int imeHeight);
 
     /**
      * Called when window manager decides to adjust the minimized state, or when the listener
@@ -45,14 +58,6 @@
     void onMinimizedStateChanged(boolean isMinimized);
 
     /**
-     * Called when window manager decides to adjust the snap-to-edge state, which determines whether
-     * to snap only to the corners of the screen or to the closest edge.  It is called when the
-     * listener is first registered to allow the listener to synchronized its state with the
-     * controller.
-     */
-    void onSnapToEdgeStateChanged(boolean isSnapToEdge);
-
-    /**
      * Called when the set of actions for the current PiP activity changes, or when the listener
      * is first registered to allow the listener to synchronized its state with the controller.
      */
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c789f8c..dd76fcf 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -337,16 +337,6 @@
     void registerPinnedStackListener(int displayId, IPinnedStackListener listener);
 
     /**
-     * Returns the initial bounds that PIP will be shown when it is first started.
-     */
-    Rect getPictureInPictureDefaultBounds(int displayId);
-
-    /**
-     * Returns the bounds that the PIP can move on the screen in the current PIP state.
-     */
-    Rect getPictureInPictureMovementBounds(int displayId);
-
-    /**
      * Updates the dim layer used while resizing.
      *
      * @param visible Whether the dim layer should be visible.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5ea5b0e..ee97984 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7333,6 +7333,25 @@
     }
 
     /**
+     * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+     * additional data.
+     * <p>
+     * This method only needs overloading if the node is marked as having extra data available.
+     * </p>
+     *
+     * @param info The info to which to add the extra data
+     * @param extraDataKey A key specifying the type of extra data to add to the info. The
+     *                     extra data should be added to the {@link Bundle} returned by
+     *                     the info's {@link AccessibilityNodeInfo#getExtras} method.
+     * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+     *
+     * @see AccessibilityNodeInfo#setExtraAvailableData
+     */
+    public void addExtraDataToAccessibilityNodeInfo(
+            AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+    }
+
+    /**
      * Determine the order in which this view will be drawn relative to its siblings for a11y
      *
      * @param info The info whose drawing order should be populated
@@ -24334,6 +24353,32 @@
         }
 
         /**
+         * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+         * additional data.
+         * <p>
+         * This method only needs to be implemented if the View offers to provide additional data.
+         * </p>
+         * <p>
+         * The default implementation behaves as
+         * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, int) for
+         * the case where no accessibility delegate is set.
+         * </p>
+         *
+         * @param host The View hosting the delegate.
+         * @param info The info to which to add the extra data
+         * @param extraDataKey A key specifying the type of extra data to add to the info. The
+         *                     extra data should be added to the {@link Bundle} returned by
+         *                     the info's {@link AccessibilityNodeInfo#getExtras} method.
+         * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+         *
+         * @see AccessibilityNodeInfo#setExtraAvailableData
+         */
+        public void addExtraDataToAccessibilityNodeInfo(
+                View host, AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+            host.addExtraDataToAccessibilityNodeInfo(info, extraDataKey, arguments);
+        }
+
+        /**
          * Called when a child of the host View has requested sending an
          * {@link AccessibilityEvent} and gives an opportunity to the parent (the host)
          * to augment the event.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index aa580fa..36beaaa 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -60,6 +60,7 @@
 import com.android.internal.util.Predicate;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -1140,31 +1141,42 @@
         final int focusableCount = views.size();
 
         final int descendantFocusability = getDescendantFocusability();
+        final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen());
 
-        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
-            if (shouldBlockFocusForTouchscreen()) {
-                focusableMode |= FOCUSABLES_TOUCH_MODE;
+        if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
+            if (focusSelf) {
+                super.addFocusables(views, direction, focusableMode);
             }
-
-            final int count = mChildrenCount;
-            final View[] children = mChildren;
-
-            for (int i = 0; i < count; i++) {
-                final View child = children[i];
-                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
-                    child.addFocusables(views, direction, focusableMode);
-                }
-            }
+            return;
         }
 
-        // we add ourselves (if focusable) in all cases except for when we are
-        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
+        if (shouldBlockFocusForTouchscreen()) {
+            focusableMode |= FOCUSABLES_TOUCH_MODE;
+        }
+
+        if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
+            super.addFocusables(views, direction, focusableMode);
+        }
+
+        int count = 0;
+        final View[] children = new View[mChildrenCount];
+        for (int i = 0; i < mChildrenCount; ++i) {
+            View child = mChildren[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                children[count++] = child;
+            }
+        }
+        Arrays.sort(children, 0, count, FocusFinder.getFocusComparator(this, false));
+        for (int i = 0; i < count; ++i) {
+            children[i].addFocusables(views, direction, focusableMode);
+        }
+
+        // When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
+        // there aren't any focusable descendants.  this is
         // to avoid the focus search finding layouts when a more precise search
         // among the focusable children would be more interesting.
-        if ((descendantFocusability != FOCUS_AFTER_DESCENDANTS
-                // No focusable descendants
-                || (focusableCount == views.size())) &&
-                (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen())) {
+        if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
+                && focusableCount == views.size()) {
             super.addFocusables(views, direction, focusableMode);
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9bfc260..9e8dda8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7492,13 +7492,13 @@
         public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
                 Region interactiveRegion, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, int flags,
-                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+                int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null && viewRootImpl.mView != null) {
                 viewRootImpl.getAccessibilityInteractionController()
                     .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
                             interactiveRegion, interactionId, callback, flags, interrogatingPid,
-                            interrogatingTid, spec);
+                            interrogatingTid, spec, args);
             } else {
                 // We cannot make the call and notify the caller so it does not wait.
                 try {
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index db2ea48..143c49a 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -554,7 +554,7 @@
     // Layer of indirection included to break dependency chain for testing
     public static class AccessibilityNodeRefresher {
         public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
-            return info.refresh(bypassCache);
+            return info.refresh(null, bypassCache);
         }
     }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 1543597..828583c 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -159,7 +159,7 @@
     public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
         return findAccessibilityNodeInfoByAccessibilityId(connectionId,
                 AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
-                false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+                false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
     }
 
     /**
@@ -259,7 +259,7 @@
      */
     public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
             int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
-            int prefetchFlags) {
+            int prefetchFlags, Bundle arguments) {
         if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
                 && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
             throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
@@ -285,9 +285,8 @@
                 final long identityToken = Binder.clearCallingIdentity();
                 final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
                         accessibilityWindowId, accessibilityNodeId, interactionId, this,
-                        prefetchFlags, Thread.currentThread().getId());
+                        prefetchFlags, Thread.currentThread().getId(), arguments);
                 Binder.restoreCallingIdentity(identityToken);
-                // If the scale is zero the call has failed.
                 if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 56d45b0..b0a11cd 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,6 +16,8 @@
 
 package android.view.accessibility;
 
+import static java.util.Collections.EMPTY_LIST;
+
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.Nullable;
@@ -517,6 +519,46 @@
      */
     public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
 
+    /**
+     * Key used to request and locate extra data for text character location. This key requests that
+     * an array of {@link android.graphics.RectF}s be added to the extras. This request is made with
+     * {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by this request are two
+     * integers: {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and
+     * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index must be valid
+     * inside the CharSequence returned by {@link #getText()}, and the length must be positive.
+     * <p>
+     * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this
+     * string as a key for {@link Bundle#getParcelableArray(String)}. The
+     * {@link android.graphics.RectF} will be null for characters that either do not exist or are
+     * off the screen.
+     *
+     * {@see #refreshWithExtraData(String, Bundle)}
+     */
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+
+    /**
+     * Integer argument specifying the start index of the requested text location data. Must be
+     * valid inside the CharSequence returned by {@link #getText()}.
+     *
+     * {@see EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY}
+     */
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+
+    /**
+     * Integer argument specifying the end index of the requested text location data. Must be
+     * positive.
+     *
+     * {@see EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY}
+     */
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+
+    /** @hide */
+    public static final String EXTRA_DATA_REQUESTED_KEY =
+            "android.view.accessibility.AccessibilityNodeInfo.extra_data_requested";
+
     // Boolean attributes.
 
     private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
@@ -651,6 +693,7 @@
     private CharSequence mError;
     private CharSequence mContentDescription;
     private String mViewIdResourceName;
+    private ArrayList<String> mExtraDataKeys;
 
     private LongArray mChildNodeIds;
     private ArrayList<AccessibilityAction> mActions;
@@ -786,14 +829,14 @@
      *
      * @hide
      */
-    public boolean refresh(boolean bypassCache) {
+    public boolean refresh(Bundle arguments, boolean bypassCache) {
         enforceSealed();
         if (!canPerformRequestOverConnection(mSourceNodeId)) {
             return false;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId(
-                mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0);
+                mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0, arguments);
         if (refreshedInfo == null) {
             return false;
         }
@@ -804,15 +847,33 @@
 
     /**
      * Refreshes this info with the latest state of the view it represents.
-     * <p>
-     * <strong>Note:</strong> If this method returns false this info is obsolete
-     * since it represents a view that is no longer in the view tree and should
-     * be recycled.
-     * </p>
-     * @return Whether the refresh succeeded.
+     *
+     * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+     * by this node is no longer in the view tree (and thus this node is obsolete and should be
+     * recycled).
      */
     public boolean refresh() {
-        return refresh(true);
+        return refresh(null, true);
+    }
+
+    /**
+     * Refreshes this info with the latest state of the view it represents, and request new
+     * data be added by the View.
+     *
+     * @param extraDataKey A bitmask of the extra data requested. Data that must be requested
+     *                     with this mechanism is generally expensive to retrieve, so should only be
+     *                     requested when needed. See
+     *                     {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY} and
+     *                     {@link #getAvailableExtraData()}.
+     * @param args A bundle of arguments for the request. These depend on the particular request.
+     *
+     * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+     * by this node is no longer in the view tree (and thus this node is obsolete and should be
+     * recycled).
+     */
+    public boolean refreshWithExtraData(String extraDataKey, Bundle args) {
+        args.putString(EXTRA_DATA_REQUESTED_KEY, extraDataKey);
+        return refresh(args, true);
     }
 
     /**
@@ -872,7 +933,7 @@
         final long childId = mChildNodeIds.get(index);
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
-                childId, false, FLAG_PREFETCH_DESCENDANTS);
+                childId, false, FLAG_PREFETCH_DESCENDANTS, null);
     }
 
     /**
@@ -1263,6 +1324,45 @@
     }
 
     /**
+     * Get the extra data available for this node.
+     * <p>
+     * Some data that is useful for some accessibility services is expensive to compute, and would
+     * place undue overhead on apps to compute all the time. That data can be requested with
+     * {@link #refreshWithExtraData(String, Bundle)}.
+     *
+     * @return An unmodifiable list of keys corresponding to extra data that can be requested.
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    public List<String> getAvailableExtraData() {
+        if (mExtraDataKeys != null) {
+            return Collections.unmodifiableList(mExtraDataKeys);
+        } else {
+            return EMPTY_LIST;
+        }
+    }
+
+    /**
+     * Set the extra data available for this node.
+     * <p>
+     * <strong>Note:</strong> When a {@code View} passes in a non-empty list, it promises that
+     * it will populate the node's extras with corresponding pieces of information in
+     * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)}.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     *
+     * @param extraDataKeys A list of types of extra data that are available.
+     * @see #getAvailableExtraData()
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setAvailableExtraData(List<String> extraDataKeys) {
+        enforceNotSealed();
+        mExtraDataKeys = new ArrayList<>(extraDataKeys);
+    }
+
+    /**
      * Sets the maximum text length, or -1 for no limit.
      * <p>
      * Typically used to indicate that an editable text field has a limit on
@@ -2658,6 +2758,14 @@
     }
 
     /**
+     * Check if a node has an extras bundle
+     * @hide
+     */
+    public boolean hasExtras() {
+        return mExtras != null;
+    }
+
+    /**
      * Gets the value of a boolean property.
      *
      * @param property The property.
@@ -2955,6 +3063,12 @@
         parcel.writeInt(mInputType);
         parcel.writeInt(mLiveRegion);
         parcel.writeInt(mDrawingOrderInParent);
+        if (mExtraDataKeys != null) {
+            parcel.writeInt(1);
+            parcel.writeStringList(mExtraDataKeys);
+        } else {
+            parcel.writeInt(0);
+        }
 
         if (mExtras != null) {
             parcel.writeInt(1);
@@ -3054,6 +3168,8 @@
         mInputType = other.mInputType;
         mLiveRegion = other.mLiveRegion;
         mDrawingOrderInParent = other.mDrawingOrderInParent;
+        mExtraDataKeys = other.mExtraDataKeys;
+
         if (other.mExtras != null) {
             mExtras = new Bundle(other.mExtras);
         } else {
@@ -3137,6 +3253,12 @@
         mDrawingOrderInParent = parcel.readInt();
 
         if (parcel.readInt() == 1) {
+            mExtraDataKeys = parcel.createStringArrayList();
+        } else {
+            mExtraDataKeys = null;
+        }
+
+        if (parcel.readInt() == 1) {
             mExtras = parcel.readBundle();
         } else {
             mExtras = null;
@@ -3455,7 +3577,7 @@
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
                 mWindowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
-                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index abcbb70..722b659 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -102,6 +102,27 @@
     }
 
     /**
+     * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+     * additional data.
+     * <p>
+     * This method only needs to be implemented if a virtual view offers to provide additional
+     * data.
+     * </p>
+     *
+     * @param virtualViewId The virtual view id used to create the node
+     * @param info The info to which to add the extra data
+     * @param extraDataKey A key specifying the type of extra data to add to the info. The
+     *                     extra data should be added to the {@link Bundle} returned by
+     *                     the info's {@link AccessibilityNodeInfo#getExtras} method.
+     * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+     *
+     * @see AccessibilityNodeInfo#setExtraAvailableData
+     */
+    public void addExtraDataToAccessibilityNodeInfo(
+            int virtualViewId, AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+    }
+
+    /**
      * Performs an accessibility action on a virtual view, i.e. a descendant of the
      * host View, with the given <code>virtualViewId</code> or the host View itself
      * if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 3287298..c390406 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -183,7 +183,7 @@
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
                 mId, AccessibilityNodeInfo.ROOT_NODE_ID,
-                true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+                true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
     }
 
     /**
@@ -209,7 +209,7 @@
 
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
-                mParentId, mAnchorId, true, 0);
+                mParentId, mAnchorId, true, 0, null);
     }
 
     /**
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index cecc4af..4c0fdfd 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -33,7 +33,8 @@
 
     void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, in Region bounds,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
-        int interrogatingPid, long interrogatingTid, in MagnificationSpec spec);
+        int interrogatingPid, long interrogatingTid, in MagnificationSpec spec,
+        in Bundle arguments);
 
     void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
         in Region bounds, int interactionId, IAccessibilityInteractionConnectionCallback callback,
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a2cb491..76ed80f 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -95,7 +95,6 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ViewParent;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -140,7 +139,6 @@
     private static final boolean DEBUG_UNDO = false;
 
     static final int BLINK = 500;
-    private static final float[] TEMP_POSITION = new float[2];
     private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
     private static final float LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS = 0.5f;
     private static final int UNSET_X_VALUE = -1;
@@ -1032,46 +1030,6 @@
                 boolean parentPositionChanged, boolean parentScrolled);
     }
 
-    private boolean isPositionVisible(final float positionX, final float positionY) {
-        synchronized (TEMP_POSITION) {
-            final float[] position = TEMP_POSITION;
-            position[0] = positionX;
-            position[1] = positionY;
-            View view = mTextView;
-
-            while (view != null) {
-                if (view != mTextView) {
-                    // Local scroll is already taken into account in positionX/Y
-                    position[0] -= view.getScrollX();
-                    position[1] -= view.getScrollY();
-                }
-
-                if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
-                        || position[1] > view.getHeight()) {
-                    return false;
-                }
-
-                if (!view.getMatrix().isIdentity()) {
-                    view.getMatrix().mapPoints(position);
-                }
-
-                position[0] += view.getLeft();
-                position[1] += view.getTop();
-
-                final ViewParent parent = view.getParent();
-                if (parent instanceof View) {
-                    view = (View) parent;
-                } else {
-                    // We've reached the ViewRoot, stop iterating
-                    view = null;
-                }
-            }
-        }
-
-        // We've been able to walk up the view hierarchy and the position was never clipped
-        return true;
-    }
-
     private boolean isOffsetVisible(int offset) {
         Layout layout = mTextView.getLayout();
         if (layout == null) return false;
@@ -1079,7 +1037,8 @@
         final int line = layout.getLineForOffset(offset);
         final int lineBottom = layout.getLineBottom(line);
         final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offset);
-        return isPositionVisible(primaryHorizontal + mTextView.viewportToContentHorizontalOffset(),
+        return mTextView.isPositionVisible(
+                primaryHorizontal + mTextView.viewportToContentHorizontalOffset(),
                 lineBottom + mTextView.viewportToContentVerticalOffset());
     }
 
@@ -4099,69 +4058,9 @@
                     final CharSequence composingText = text.subSequence(composingTextStart,
                             composingTextEnd);
                     builder.setComposingText(composingTextStart, composingText);
-
-                    final int minLine = layout.getLineForOffset(composingTextStart);
-                    final int maxLine = layout.getLineForOffset(composingTextEnd - 1);
-                    for (int line = minLine; line <= maxLine; ++line) {
-                        final int lineStart = layout.getLineStart(line);
-                        final int lineEnd = layout.getLineEnd(line);
-                        final int offsetStart = Math.max(lineStart, composingTextStart);
-                        final int offsetEnd = Math.min(lineEnd, composingTextEnd);
-                        final boolean ltrLine =
-                                layout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
-                        final float[] widths = new float[offsetEnd - offsetStart];
-                        layout.getPaint().getTextWidths(text, offsetStart, offsetEnd, widths);
-                        final float top = layout.getLineTop(line);
-                        final float bottom = layout.getLineBottom(line);
-                        for (int offset = offsetStart; offset < offsetEnd; ++offset) {
-                            final float charWidth = widths[offset - offsetStart];
-                            final boolean isRtl = layout.isRtlCharAt(offset);
-                            final float primary = layout.getPrimaryHorizontal(offset);
-                            final float secondary = layout.getSecondaryHorizontal(offset);
-                            // TODO: This doesn't work perfectly for text with custom styles and
-                            // TAB chars.
-                            final float left;
-                            final float right;
-                            if (ltrLine) {
-                                if (isRtl) {
-                                    left = secondary - charWidth;
-                                    right = secondary;
-                                } else {
-                                    left = primary;
-                                    right = primary + charWidth;
-                                }
-                            } else {
-                                if (!isRtl) {
-                                    left = secondary;
-                                    right = secondary + charWidth;
-                                } else {
-                                    left = primary - charWidth;
-                                    right = primary;
-                                }
-                            }
-                            // TODO: Check top-right and bottom-left as well.
-                            final float localLeft = left + viewportToContentHorizontalOffset;
-                            final float localRight = right + viewportToContentHorizontalOffset;
-                            final float localTop = top + viewportToContentVerticalOffset;
-                            final float localBottom = bottom + viewportToContentVerticalOffset;
-                            final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
-                            final boolean isBottomRightVisible =
-                                    isPositionVisible(localRight, localBottom);
-                            int characterBoundsFlags = 0;
-                            if (isTopLeftVisible || isBottomRightVisible) {
-                                characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
-                            }
-                            if (!isTopLeftVisible || !isBottomRightVisible) {
-                                characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
-                            }
-                            if (isRtl) {
-                                characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
-                            }
-                            // Here offset is the index in Java chars.
-                            builder.addCharacterBounds(offset, localLeft, localTop, localRight,
-                                    localBottom, characterBoundsFlags);
-                        }
-                    }
+                    mTextView.populateCharacterBounds(builder, composingTextStart,
+                            composingTextEnd, viewportToContentHorizontalOffset,
+                            viewportToContentVerticalOffset);
                 }
             }
 
@@ -4177,10 +4076,10 @@
                         + viewportToContentVerticalOffset;
                 final float insertionMarkerBottom = layout.getLineBottom(line)
                         + viewportToContentVerticalOffset;
-                final boolean isTopVisible =
-                        isPositionVisible(insertionMarkerX, insertionMarkerTop);
-                final boolean isBottomVisible =
-                        isPositionVisible(insertionMarkerX, insertionMarkerBottom);
+                final boolean isTopVisible = mTextView
+                        .isPositionVisible(insertionMarkerX, insertionMarkerTop);
+                final boolean isBottomVisible = mTextView
+                        .isPositionVisible(insertionMarkerX, insertionMarkerBottom);
                 int insertionMarkerFlags = 0;
                 if (isTopVisible || isBottomVisible) {
                     insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
@@ -4388,7 +4287,8 @@
                 return false;
             }
 
-            return isPositionVisible(mPositionX + mHotspotX + getHorizontalOffset(), mPositionY);
+            return mTextView.isPositionVisible(
+                    mPositionX + mHotspotX + getHorizontalOffset(), mPositionY);
         }
 
         public abstract int getCurrentCursorOffset();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7ef0a06..b18ca45 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,6 +17,10 @@
 package android.widget;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
 
 import android.R;
 import android.annotation.ColorInt;
@@ -142,6 +146,7 @@
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
@@ -166,6 +171,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Locale;
 
 /**
@@ -268,6 +274,7 @@
     static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
     static final boolean DEBUG_AUTOFILL = false;
+    private static final float[] TEMP_POSITION = new float[2];
 
     // Enum for the "typeface" XML parameter.
     // TODO: How can we get this from the XML instead of hardcoding it here?
@@ -9906,6 +9913,8 @@
                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+            info.setAvailableExtraData(
+                    Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
         }
 
         if (isFocused()) {
@@ -9942,6 +9951,164 @@
         }
     }
 
+    @Override
+    public void addExtraDataToAccessibilityNodeInfo(
+            AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+        if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
+            int positionInfoStartIndex = arguments.getInt(
+                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
+            int positionInfoLength = arguments.getInt(
+                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
+            if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
+                    || (positionInfoStartIndex >= mText.length())) {
+                Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
+                return;
+            }
+            RectF[] boundingRects = new RectF[positionInfoLength];
+            final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+            populateCharacterBounds(builder, positionInfoStartIndex,
+                    positionInfoStartIndex + positionInfoLength,
+                    viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
+            CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
+            if (mTempRect == null) mTempRect = new Rect();
+            Rect viewBoundsInScreen = mTempRect;
+            info.getBoundsInScreen(viewBoundsInScreen);
+            for (int i = 0; i < positionInfoLength; i++) {
+                int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
+                if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
+                    RectF bounds = cursorAnchorInfo
+                            .getCharacterBounds(positionInfoStartIndex + i);
+                    if (bounds != null) {
+                        bounds.offset(viewBoundsInScreen.left, viewBoundsInScreen.top);
+                        boundingRects[i] = bounds;
+                    }
+                }
+            }
+            info.getExtras().putParcelableArray(extraDataKey, boundingRects);
+        }
+    }
+
+    /**
+     * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
+     *
+     * @param builder The builder to populate
+     * @param startIndex The starting character index to populate
+     * @param endIndex The ending character index to populate
+     * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
+     * content
+     * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
+     * @hide
+     */
+    public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
+            int startIndex, int endIndex, float viewportToContentHorizontalOffset,
+            float viewportToContentVerticalOffset) {
+        final int minLine = mLayout.getLineForOffset(startIndex);
+        final int maxLine = mLayout.getLineForOffset(endIndex - 1);
+        for (int line = minLine; line <= maxLine; ++line) {
+            final int lineStart = mLayout.getLineStart(line);
+            final int lineEnd = mLayout.getLineEnd(line);
+            final int offsetStart = Math.max(lineStart, startIndex);
+            final int offsetEnd = Math.min(lineEnd, endIndex);
+            final boolean ltrLine =
+                    mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
+            final float[] widths = new float[offsetEnd - offsetStart];
+            mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths);
+            final float top = mLayout.getLineTop(line);
+            final float bottom = mLayout.getLineBottom(line);
+            for (int offset = offsetStart; offset < offsetEnd; ++offset) {
+                final float charWidth = widths[offset - offsetStart];
+                final boolean isRtl = mLayout.isRtlCharAt(offset);
+                final float primary = mLayout.getPrimaryHorizontal(offset);
+                final float secondary = mLayout.getSecondaryHorizontal(offset);
+                // TODO: This doesn't work perfectly for text with custom styles and
+                // TAB chars.
+                final float left;
+                final float right;
+                if (ltrLine) {
+                    if (isRtl) {
+                        left = secondary - charWidth;
+                        right = secondary;
+                    } else {
+                        left = primary;
+                        right = primary + charWidth;
+                    }
+                } else {
+                    if (!isRtl) {
+                        left = secondary;
+                        right = secondary + charWidth;
+                    } else {
+                        left = primary - charWidth;
+                        right = primary;
+                    }
+                }
+                // TODO: Check top-right and bottom-left as well.
+                final float localLeft = left + viewportToContentHorizontalOffset;
+                final float localRight = right + viewportToContentHorizontalOffset;
+                final float localTop = top + viewportToContentVerticalOffset;
+                final float localBottom = bottom + viewportToContentVerticalOffset;
+                final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
+                final boolean isBottomRightVisible =
+                        isPositionVisible(localRight, localBottom);
+                int characterBoundsFlags = 0;
+                if (isTopLeftVisible || isBottomRightVisible) {
+                    characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
+                }
+                if (!isTopLeftVisible || !isBottomRightVisible) {
+                    characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+                }
+                if (isRtl) {
+                    characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+                }
+                // Here offset is the index in Java chars.
+                builder.addCharacterBounds(offset, localLeft, localTop, localRight,
+                        localBottom, characterBoundsFlags);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isPositionVisible(final float positionX, final float positionY) {
+        synchronized (TEMP_POSITION) {
+            final float[] position = TEMP_POSITION;
+            position[0] = positionX;
+            position[1] = positionY;
+            View view = this;
+
+            while (view != null) {
+                if (view != this) {
+                    // Local scroll is already taken into account in positionX/Y
+                    position[0] -= view.getScrollX();
+                    position[1] -= view.getScrollY();
+                }
+
+                if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
+                        || position[1] > view.getHeight()) {
+                    return false;
+                }
+
+                if (!view.getMatrix().isIdentity()) {
+                    view.getMatrix().mapPoints(position);
+                }
+
+                position[0] += view.getLeft();
+                position[1] += view.getTop();
+
+                final ViewParent parent = view.getParent();
+                if (parent instanceof View) {
+                    view = (View) parent;
+                } else {
+                    // We've reached the ViewRoot, stop iterating
+                    view = null;
+                }
+            }
+        }
+
+        // We've been able to walk up the view hierarchy and the position was never clipped
+        return true;
+    }
+
     /**
      * Performs an accessibility action after it has been offered to the
      * delegate.
diff --git a/core/java/com/android/internal/policy/PipMotionHelper.java b/core/java/com/android/internal/policy/PipMotionHelper.java
deleted file mode 100644
index 944cd32..0000000
--- a/core/java/com/android/internal/policy/PipMotionHelper.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.policy;
-
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * A helper to animate the PIP.
- */
-public class PipMotionHelper {
-
-    private static final String TAG = "PipMotionHelper";
-
-    private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
-    private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-    private static final int DEFAULT_DURATION = 225;
-
-    private IActivityManager mActivityManager;
-    private Handler mHandler;
-
-    public PipMotionHelper(Handler handler) {
-        mHandler = handler;
-    }
-
-    /**
-     * Moves the PIP to give given {@param bounds}.
-     */
-    public void resizeToBounds(Rect toBounds) {
-        mHandler.post(() -> {
-            if (mActivityManager == null) {
-                mActivityManager = ActivityManager.getService();
-            }
-            try {
-                mActivityManager.resizePinnedStack(toBounds, null /* tempPinnedTaskBounds */);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not move pinned stack to bounds: " + toBounds, e);
-            }
-        });
-    }
-
-    /**
-     * Creates an animation to move the PIP to give given {@param toBounds} with the default
-     * animation properties.
-     */
-    public ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds) {
-        return createAnimationToBounds(fromBounds, toBounds, DEFAULT_DURATION, FAST_OUT_SLOW_IN,
-                null);
-    }
-
-    /**
-     * Creates an animation to move the PIP to give given {@param toBounds}.
-     */
-    public ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration,
-            Interpolator interpolator, ValueAnimator.AnimatorUpdateListener updateListener) {
-        ValueAnimator anim = ValueAnimator.ofObject(RECT_EVALUATOR, fromBounds, toBounds);
-        anim.setDuration(duration);
-        anim.setInterpolator(interpolator);
-        anim.addUpdateListener((ValueAnimator animation) -> {
-            resizeToBounds((Rect) animation.getAnimatedValue());
-        });
-        if (updateListener != null) {
-            anim.addUpdateListener(updateListener);
-        }
-        return anim;
-    }
-
-
-}
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index ec92aa9..bf047c1 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -84,13 +84,6 @@
     }
 
     /**
-     * Enables snapping to the closest edge.
-     */
-    public void setSnapToEdge(boolean snapToEdge) {
-        mSnapMode = snapToEdge ? SNAP_MODE_EDGE : mDefaultSnapMode;
-    }
-
-    /**
      * @return the closest absolute snap stack bounds for the given {@param stackBounds} moving at
      * the given {@param velocityX} and {@param velocityY}.  The {@param movementBounds} should be
      * those for the given {@param stackBounds}.
@@ -233,6 +226,21 @@
     }
 
     /**
+     * Adjusts {@param movementBoundsOut} so that it is the movement bounds for the given
+     * {@param stackBounds}.
+     */
+    public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
+            int imeHeight) {
+        // Adjust the right/bottom to ensure the stack bounds never goes offscreen
+        movementBoundsOut.set(insetBounds);
+        movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right -
+                stackBounds.width());
+        movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom -
+                stackBounds.height());
+        movementBoundsOut.bottom -= imeHeight;
+    }
+
+    /**
      * @return the closest point in {@param points} to the given {@param x} and {@param y}.
      */
     private Point findClosestPoint(int x, int y, Point[] points) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a6e43ff..7b800b3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3220,6 +3220,11 @@
     <permission android:name="android.permission.BIND_VR_LISTENER_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by system apps when accessing restricted VR APIs.
+         <p>Protection level: signature -->
+    <permission android:name="android.permission.RESTRICTED_VR_ACCESS"
+        android:protectionLevel="signature|preinstalled" />
+
     <!-- Required to make calls to {@link android.service.vr.IVrManager}.
          @hide -->
     <permission android:name="android.permission.ACCESS_VR_MANAGER"
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index f5cedfa..b490545 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -583,4 +583,17 @@
         right = in.readFloat();
         bottom = in.readFloat();
     }
+
+    /**
+     * Scales up the rect by the given scale.
+     * @hide
+     */
+    public void scale(float scale) {
+        if (scale != 1.0f) {
+            left = left * scale;
+            top = top * scale ;
+            right = right * scale;
+            bottom = bottom * scale;
+        }
+    }
 }
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 1dad58f..71bee93 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -205,6 +205,10 @@
     *d++ = a * (start.g * oppAmount + end.g * amount);
     *d++ = a * (start.b * oppAmount + end.b * amount);
 #else
+    // What we're doing to the alpha channel here is technically incorrect
+    // but reproduces Android's old behavior when the alpha was pre-multiplied
+    // with gamma-encoded colors
+    a = EOCF_sRGB(a);
     *d++ = a * OECF_sRGB(start.r * oppAmount + end.r * amount);
     *d++ = a * OECF_sRGB(start.g * oppAmount + end.g * amount);
     *d++ = a * OECF_sRGB(start.b * oppAmount + end.b * amount);
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 7107679..42ef762 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -164,24 +164,39 @@
 // Dithering must be done in the quantization space
 // When we are writing to an sRGB framebuffer, we must do the following:
 //     EOCF(OECF(color) + dither)
-// We approximate the transfer functions with gamma 2.0 to avoid branches and pow()
 // The dithering pattern is generated with a triangle noise generator in the range [-0.0,1.0]
 // TODO: Handle linear fp16 render targets
-const char* gFS_Gradient_Functions =
-        "\nfloat triangleNoise(const highp vec2 n) {\n"
-        "    highp vec2 p = fract(n * vec2(5.3987, 5.4421));\n"
-        "    p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));\n"
-        "    highp float xy = p.x * p.y;\n"
-        "    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
-        "}\n";
+const char* gFS_Gradient_Functions = R"__SHADER__(
+        float triangleNoise(const highp vec2 n) {
+            highp vec2 p = fract(n * vec2(5.3987, 5.4421));
+            p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));
+            highp float xy = p.x * p.y;
+            return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+        }
+
+        float OECF_sRGB(const float linear) {
+            // IEC 61966-2-1:1999
+            return linear <= 0.0031308 ? linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
+        }
+
+        vec3 OECF_sRGB(const vec3 linear) {
+            return vec3(OECF_sRGB(linear.r), OECF_sRGB(linear.g), OECF_sRGB(linear.b));
+        }
+
+        float EOCF_sRGB(float srgb) {
+            // IEC 61966-2-1:1999
+            return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
+        }
+)__SHADER__";
 const char* gFS_Gradient_Preamble[2] = {
         // Linear framebuffer
         "\nvec4 dither(const vec4 color) {\n"
         "    return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);\n"
         "}\n"
         "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n"
-        "    vec4 c = pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));\n"
-        "    return vec4(c.rgb * c.a, c.a);\n"
+        "    vec4 c = mix(a, b, v);\n"
+        "    c.a = EOCF_sRGB(c.a);\n" // This is technically incorrect but preserves compatibility
+        "    return vec4(OECF_sRGB(c.rgb) * c.a, c.a);\n"
         "}\n",
         // sRGB framebuffer
         "\nvec4 dither(const vec4 color) {\n"
@@ -200,13 +215,15 @@
 // The gamma coefficient is chosen to thicken or thin the text accordingly
 // The dot product used to compute the luminance could be approximated with
 // a simple max(color.r, color.g, color.b)
-const char* gFS_Gamma_Preamble =
-        "\n#define GAMMA (%.2f)\n"
-        "#define GAMMA_INV (%.2f)\n"
-        "\nfloat gamma(float a, const vec3 color) {\n"
-        "    float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n"
-        "    return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);\n"
-        "}\n";
+const char* gFS_Gamma_Preamble = R"__SHADER__(
+        #define GAMMA (%.2f)
+        #define GAMMA_INV (%.2f)
+
+        float gamma(float a, const vec3 color) {
+            float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
+            return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);
+        }
+)__SHADER__";
 
 const char* gFS_Main =
         "\nvoid main(void) {\n"
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 705395e..f6850a1 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -44,7 +44,7 @@
     case GL_RGBA16F:
         return 8;
     default:
-        LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat);
+        LOG_ALWAYS_FATAL("UNKNOWN FORMAT 0x%x", glFormat);
     }
 }
 
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
new file mode 100644
index 0000000..a63a585
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+#include <SkColorMatrixFilter.h>
+#include <SkGradientShader.h>
+
+class SimpleColorMatrixAnimation;
+
+static TestScene::Registrar _SimpleColorMatrix(TestScene::Info{
+    "simpleColorMatrix",
+    "A color matrix shader benchmark for the simple scale/translate case, which has R, G, and B "
+    "all scaled and translated the same amount.",
+    TestScene::simpleCreateScene<SimpleColorMatrixAnimation>
+});
+
+class SimpleColorMatrixAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, Canvas& canvas) override {
+        canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+
+        sp<RenderNode> card = createCard(0, 0, width, height);
+        canvas.drawRenderNode(card.get());
+        cards.push_back(card);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 20;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, Canvas& canvas) {
+            SkPaint paint;
+            float matrix[20] = { 0 };
+
+            // Simple scale/translate case where R, G, and B are all treated equivalently
+            matrix[SkColorMatrix::kR_Scale] = 1.1f;
+            matrix[SkColorMatrix::kG_Scale] = 1.1f;
+            matrix[SkColorMatrix::kB_Scale] = 1.1f;
+            matrix[SkColorMatrix::kA_Scale] = 0.5f;
+
+            matrix[SkColorMatrix::kR_Trans] = 5.0f;
+            matrix[SkColorMatrix::kG_Trans] = 5.0f;
+            matrix[SkColorMatrix::kB_Trans] = 5.0f;
+            matrix[SkColorMatrix::kA_Trans] = 10.0f;
+
+            paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+
+            // set a shader so it's not likely for the matrix to be optimized away (since a clever
+            // enough renderer might apply it directly to the paint color)
+            float pos[] = { 0, 1 };
+            SkPoint pts[] = { SkPoint::Make(0, 0), SkPoint::Make(width, height) };
+            SkColor colors[2] = { Color::DeepPurple_500, Color::DeepOrange_500 };
+            paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2,
+                SkShader::kClamp_TileMode));
+
+            // overdraw several times to emphasize shader cost
+            for (int i = 0; i < 10; i++) {
+                canvas.drawRect(i, i, width, height, paint);
+            }
+        });
+    }
+};
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
new file mode 100644
index 0000000..053eb6d
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+
+#include "TestSceneBase.h"
+
+#include <SkGradientShader.h>
+
+class SimpleGradientAnimation;
+
+static TestScene::Registrar _SimpleGradient(TestScene::Info{
+    "simpleGradient",
+    "A benchmark of shader performance of linear, 2 color gradients with black in them.",
+    TestScene::simpleCreateScene<SimpleGradientAnimation>
+});
+
+class SimpleGradientAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, Canvas& canvas) override {
+        canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+
+        sp<RenderNode> card = createCard(0, 0, width, height);
+        canvas.drawRenderNode(card.get());
+        cards.push_back(card);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 20;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, Canvas& canvas) {
+            float pos[] = { 0, 1 };
+            SkPoint pts[] = { SkPoint::Make(0, 0), SkPoint::Make(width, height) };
+            SkPaint paint;
+            // overdraw several times to emphasize shader cost
+            for (int i = 0; i < 10; i++) {
+                // use i%2 start position to pick 2 color combo with black in it
+                SkColor colors[3] = { Color::Transparent, Color::Black, Color::Cyan_500 };
+                paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2,
+                    SkShader::kClamp_TileMode));
+                canvas.drawRect(i, i, width, height, paint);
+            }
+        });
+    }
+};
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index cd38b50..9083c16 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1956,7 +1956,13 @@
                         e.printStackTrace();
                     }
                     baseSetStartDelayMs(0);
-                    startImpl();
+                    try {
+                        startImpl();
+                    } catch (IllegalStateException e) {
+                        // fail silently for a state exception when it is happening after
+                        // a delayed start, as the player state could have changed between the
+                        // call to start() and the execution of startImpl()
+                    }
                 }
             }.start();
         }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 03dc2ea..1a1d0f3 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1258,7 +1258,13 @@
                         e.printStackTrace();
                     }
                     baseSetStartDelayMs(0);
-                    startImpl();
+                    try {
+                        startImpl();
+                    } catch (IllegalStateException e) {
+                        // fail silently for a state exception when it is happening after
+                        // a delayed start, as the player state could have changed between the
+                        // call to start() and the execution of startImpl()
+                    }
                 }
             }.start();
         }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2cc2621..8553952 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1763,14 +1763,6 @@
         not appear on production builds ever. -->
     <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string>
 
-    <!-- PIP allow minimize title. Non-translatable since it should
-        not appear on production builds ever. -->
-    <string name="pip_allow_minimize_title" translatable="false">Allow PIP to minimize</string>
-
-    <!-- PIP allow minimize description. Non-translatable since it should
-        not appear on production builds ever. -->
-    <string name="pip_allow_minimize_summary" translatable="false">Allow PIP to minimize slightly offscreen</string>
-
     <!-- Tuner string -->
     <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
     <!-- Tuner string -->
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 94a7c07..6198ab7 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -131,12 +131,6 @@
           android:summary="@string/pip_drag_to_dismiss_summary"
           sysui:defValue="false" />
 
-        <com.android.systemui.tuner.TunerSwitch
-            android:key="pip_allow_minimize"
-            android:title="@string/pip_allow_minimize_title"
-            android:summary="@string/pip_allow_minimize_summary"
-            sysui:defValue="true" />
-
     </PreferenceScreen>
 
     <PreferenceScreen
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index e182176..beec137 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -17,18 +17,15 @@
 package com.android.systemui.pip.phone;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.view.TouchDelegate;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
 import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
 import android.widget.FrameLayout;
 
 import com.android.systemui.Interpolators;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 3df557d..f59b2a4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,20 +16,16 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackInfo;
-import android.app.ActivityOptions;
 import android.app.IActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
@@ -94,7 +90,7 @@
                 }
             }
             if (expandPipToFullscreen) {
-                mTouchHandler.expandPinnedStackToFullscreen();
+                mTouchHandler.getMotionHelper().expandPip();
             } else {
                 Log.w(TAG, "Can not expand PiP to fullscreen via intent from the same package.");
             }
@@ -114,28 +110,31 @@
         }
 
         @Override
-        public void onBoundsChanged(boolean adjustedForIme) {
-            // Do nothing
-        }
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) {
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mHandler.post(() -> {
-                mMenuController.setAppActions(actions);
+                mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
 
         @Override
         public void onMinimizedStateChanged(boolean isMinimized) {
             mHandler.post(() -> {
-                mTouchHandler.onMinimizedStateChanged(isMinimized);
+                mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
             });
         }
 
         @Override
-        public void onSnapToEdgeStateChanged(boolean isSnapToEdge) {
+        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+                boolean fromImeAdjustement) {
             mHandler.post(() -> {
-                mTouchHandler.onSnapToEdgeStateChanged(isSnapToEdge);
+                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, fromImeAdjustement);
+            });
+        }
+
+        @Override
+        public void onActionsChanged(ParceledListSlice actions) {
+            mHandler.post(() -> {
+                mMenuController.setAppActions(actions);
             });
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
index d96baa6..5a665a9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
@@ -16,16 +16,22 @@
 
 package com.android.systemui.pip.phone;
 
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
 import android.app.IActivityManager;
+import android.app.PendingIntent;
 import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.drawable.Icon;
 import android.media.session.MediaController;
-import android.media.session.MediaController.TransportControls;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.os.UserHandle;
 
 import com.android.systemui.R;
 
@@ -40,6 +46,9 @@
  */
 public class PipMediaController {
 
+    private static final String ACTION_PLAY = "com.android.systemui.pip.phone.PLAY";
+    private static final String ACTION_PAUSE = "com.android.systemui.pip.phone.PAUSE";
+
     /**
      * A listener interface to receive notification on changes to the media actions.
      */
@@ -59,6 +68,18 @@
     private RemoteAction mPauseAction;
     private RemoteAction mPlayAction;
 
+    private BroadcastReceiver mPlayPauseActionReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(ACTION_PLAY)) {
+                mMediaController.getTransportControls().play();
+            } else if (action.equals(ACTION_PAUSE)) {
+                mMediaController.getTransportControls().pause();
+            }
+        }
+    };
+
     private MediaController.Callback mPlaybackChangedListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -73,6 +94,10 @@
     public PipMediaController(Context context, IActivityManager activityManager) {
         mContext = context;
         mActivityManager = activityManager;
+        IntentFilter mediaControlFilter = new IntentFilter();
+        mediaControlFilter.addAction(ACTION_PLAY);
+        mediaControlFilter.addAction(ACTION_PAUSE);
+        mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter);
 
         createMediaActions();
         mMediaSessionManager =
@@ -135,12 +160,14 @@
         String pauseDescription = mContext.getString(R.string.pip_pause);
         mPauseAction = new RemoteAction(Icon.createWithResource(mContext,
                 R.drawable.ic_pause_white_24dp), pauseDescription, pauseDescription,
-                action -> mMediaController.getTransportControls().pause());
+                        PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE),
+                                FLAG_UPDATE_CURRENT));
 
         String playDescription = mContext.getString(R.string.pip_play);
         mPlayAction = new RemoteAction(Icon.createWithResource(mContext,
                 R.drawable.ic_play_arrow_white_24dp), playDescription, playDescription,
-                action -> mMediaController.getTransportControls().play());
+                        PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY),
+                                FLAG_UPDATE_CURRENT));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 438b8dd..9066977 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
 import android.app.RemoteAction;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
@@ -258,7 +259,11 @@
                     }, mHandler);
                     actionView.setContentDescription(action.getContentDescription());
                     actionView.setOnClickListener(v -> {
-                        action.sendActionInvoked();
+                        try {
+                            action.getActionIntent().send();
+                        } catch (CanceledException e) {
+                            Log.w(TAG, "Failed to send action", e);
+                        }
                     });
                     actionsGroup.addView(actionView);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
new file mode 100644
index 0000000..28ab3fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip.phone;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.ActivityManager.StackInfo;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+/**
+ * A helper to animate and manipulate the PiP.
+ */
+public class PipMotionHelper {
+
+    private static final String TAG = "PipMotionHelper";
+
+    private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
+
+    private static final int DEFAULT_MOVE_STACK_DURATION = 225;
+    private static final int SNAP_STACK_DURATION = 225;
+    private static final int DISMISS_STACK_DURATION = 375;
+    private static final int SHRINK_STACK_FROM_MENU_DURATION = 175;
+    private static final int EXPAND_STACK_TO_MENU_DURATION = 175;
+    private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 225;
+    private static final int MINIMIZE_STACK_MAX_DURATION = 200;
+
+    // The fraction of the stack width that the user has to drag offscreen to minimize the PiP
+    private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f;
+
+    private Context mContext;
+    private IActivityManager mActivityManager;
+    private Handler mHandler;
+
+    private PipSnapAlgorithm mSnapAlgorithm;
+    private FlingAnimationUtils mFlingAnimationUtils;
+
+    private final Rect mBounds = new Rect();
+    private final Rect mStableInsets = new Rect();
+
+    private ValueAnimator mBoundsAnimator = null;
+    private ValueAnimator.AnimatorUpdateListener mUpdateBoundsListener =
+            new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mBounds.set((Rect) animation.getAnimatedValue());
+                }
+            };
+
+    public PipMotionHelper(Context context, IActivityManager activityManager,
+            PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils) {
+        mContext = context;
+        mHandler = BackgroundThread.getHandler();
+        mActivityManager = activityManager;
+        mSnapAlgorithm = snapAlgorithm;
+        mFlingAnimationUtils = flingAnimationUtils;
+        onConfigurationChanged();
+    }
+
+    /**
+     * Updates whenever the configuration changes.
+     */
+    void onConfigurationChanged() {
+        mSnapAlgorithm.onConfigurationChanged();
+        SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
+    }
+
+    /**
+     * Synchronizes the current bounds with the pinned stack.
+     */
+    void synchronizePinnedStackBounds() {
+        cancelAnimations();
+        try {
+            StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            mBounds.set(stackInfo.bounds);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to get pinned stack bounds");
+        }
+    }
+
+    /**
+     * Tries to the move the pinned stack to the given {@param bounds}.
+     */
+    void movePip(Rect toBounds) {
+        cancelAnimations();
+        resizePipUnchecked(toBounds);
+        mBounds.set(toBounds);
+    }
+
+    /**
+     * Resizes the pinned stack back to fullscreen.
+     */
+    void expandPip() {
+        cancelAnimations();
+        mHandler.post(() -> {
+            try {
+                mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
+                        true /* allowResizeInDockedMode */, true /* preserveWindows */,
+                        true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error showing PiP menu activity", e);
+            }
+        });
+    }
+
+    /**
+     * Dismisses the pinned stack.
+     */
+    void dismissPip() {
+        cancelAnimations();
+        mHandler.post(() -> {
+            try {
+                mActivityManager.removeStack(PINNED_STACK_ID);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to remove PiP", e);
+            }
+        });
+    }
+
+    /**
+     * @return the PiP bounds.
+     */
+    Rect getBounds() {
+        return mBounds;
+    }
+
+    /**
+     * @return the closest minimized PiP bounds.
+     */
+    Rect getClosestMinimizedBounds(Rect stackBounds, Rect movementBounds) {
+        Point displaySize = new Point();
+        mContext.getDisplay().getRealSize(displaySize);
+        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, stackBounds);
+        mSnapAlgorithm.applyMinimizedOffset(toBounds, movementBounds, displaySize, mStableInsets);
+        return toBounds;
+    }
+
+    /**
+     * @return whether the PiP at the current bounds should be minimized.
+     */
+    boolean shouldMinimizePip() {
+        Point displaySize = new Point();
+        mContext.getDisplay().getRealSize(displaySize);
+        if (mBounds.left < 0) {
+            float offscreenFraction = (float) -mBounds.left / mBounds.width();
+            return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
+        } else if (mBounds.right > displaySize.x) {
+            float offscreenFraction = (float) (mBounds.right - displaySize.x) /
+                    mBounds.width();
+            return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Flings the minimized PiP to the closest minimized snap target.
+     */
+    Rect flingToMinimizedState(float velocityY, Rect movementBounds) {
+        cancelAnimations();
+        // We currently only allow flinging the minimized stack up and down, so just lock the
+        // movement bounds to the current stack bounds horizontally
+        movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
+                movementBounds.bottom);
+        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
+                0 /* velocityX */, velocityY);
+        if (!mBounds.equals(toBounds)) {
+            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN,
+                    mUpdateBoundsListener);
+            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
+                    distanceBetweenRectOffsets(mBounds, toBounds),
+                    velocityY);
+            mBoundsAnimator.start();
+        }
+        return toBounds;
+    }
+
+    /**
+     * Animates the PiP to the minimized state, slightly offscreen.
+     */
+    Rect animateToClosestMinimizedState(Rect movementBounds,
+            final PipMenuActivityController menuController) {
+        cancelAnimations();
+        Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
+        if (!mBounds.equals(toBounds)) {
+            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds,
+                    MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, mUpdateBoundsListener);
+            mBoundsAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    menuController.hideMenu();
+                }
+            });
+            mBoundsAnimator.start();
+        }
+        return toBounds;
+    }
+
+    /**
+     * Flings the PiP to the closest snap target.
+     */
+    Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds) {
+        cancelAnimations();
+        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
+                velocityX, velocityY);
+        if (!mBounds.equals(toBounds)) {
+            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN,
+                    mUpdateBoundsListener);
+            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
+                    distanceBetweenRectOffsets(mBounds, toBounds),
+                    velocity);
+            mBoundsAnimator.start();
+        }
+        return toBounds;
+    }
+
+    /**
+     * Animates the PiP to the closest snap target.
+     */
+    Rect animateToClosestSnapTarget(Rect movementBounds) {
+        cancelAnimations();
+        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
+        if (!mBounds.equals(toBounds)) {
+            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
+                    FAST_OUT_SLOW_IN, mUpdateBoundsListener);
+            mBoundsAnimator.start();
+        }
+        return toBounds;
+    }
+
+    /**
+     * Animates the PiP to the expanded state to show the menu.
+     */
+    float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
+            Rect expandedMovementBounds) {
+        float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), movementBounds);
+        mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
+        mBoundsAnimator = createAnimationToBounds(mBounds, expandedBounds,
+                EXPAND_STACK_TO_MENU_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener);
+        mBoundsAnimator.start();
+        return savedSnapFraction;
+    }
+
+    /**
+     * Animates the PiP from the expanded state to the normal state after the menu is hidden.
+     */
+    void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
+            Rect normalMovementBounds) {
+        if (savedSnapFraction >= 0f) {
+            mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction);
+            mBoundsAnimator = createAnimationToBounds(mBounds, normalBounds,
+                    SHRINK_STACK_FROM_MENU_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener);
+            mBoundsAnimator.start();
+        } else {
+            animateToClosestSnapTarget(normalMovementBounds);
+        }
+    }
+
+    /**
+     * Animates the dismissal of the PiP over the dismiss target bounds.
+     */
+    Rect animateDismissFromDrag(Rect dismissBounds) {
+        cancelAnimations();
+        Rect toBounds = new Rect(dismissBounds.centerX(),
+                dismissBounds.centerY(),
+                dismissBounds.centerX() + 1,
+                dismissBounds.centerY() + 1);
+        mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DISMISS_STACK_DURATION,
+                FAST_OUT_LINEAR_IN, mUpdateBoundsListener);
+        mBoundsAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                dismissPip();
+            }
+        });
+        mBoundsAnimator.start();
+        return toBounds;
+    }
+
+    /**
+     * Animates the PiP to some given bounds.
+     */
+    void animateToBounds(Rect toBounds) {
+        cancelAnimations();
+        if (!mBounds.equals(toBounds)) {
+            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds,
+                    DEFAULT_MOVE_STACK_DURATION, FAST_OUT_LINEAR_IN, mUpdateBoundsListener);
+            mBoundsAnimator.start();
+        }
+    }
+
+    /**
+     * Cancels all existing animations.
+     */
+    void cancelAnimations() {
+        if (mBoundsAnimator != null) {
+            mBoundsAnimator.cancel();
+            mBoundsAnimator = null;
+        }
+    }
+
+    /**
+     * Creates an animation to move the PiP to give given {@param toBounds}.
+     */
+    private ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration,
+            Interpolator interpolator, ValueAnimator.AnimatorUpdateListener updateListener) {
+        ValueAnimator anim = ValueAnimator.ofObject(RECT_EVALUATOR, fromBounds, toBounds);
+        anim.setDuration(duration);
+        anim.setInterpolator(interpolator);
+        anim.addUpdateListener((ValueAnimator animation) -> {
+            resizePipUnchecked((Rect) animation.getAnimatedValue());
+        });
+        if (updateListener != null) {
+            anim.addUpdateListener(updateListener);
+        }
+        return anim;
+    }
+
+    /**
+     * Directly resizes the PiP to the given {@param bounds}.
+     */
+    private void resizePipUnchecked(Rect toBounds) {
+        if (!toBounds.equals(mBounds)) {
+            mHandler.post(() -> {
+                try {
+                    mActivityManager.resizePinnedStack(toBounds, null /* tempPinnedTaskBounds */);
+                    mBounds.set(toBounds);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not move pinned stack to bounds: " + toBounds, e);
+                }
+            });
+        }
+    }
+
+    /**
+     * @return the distance between points {@param p1} and {@param p2}.
+     */
+    private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
+        return PointF.length(r1.left - r2.left, r1.top - r2.top);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 10393c6..b3adee0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -16,21 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
-import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
-import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -47,8 +36,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.policy.PipMotionHelper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -60,24 +47,15 @@
  */
 public class PipTouchHandler implements TunerService.Tunable {
     private static final String TAG = "PipTouchHandler";
-    private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false;
 
     // These values are used for metrics and should never change
     private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
     private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
 
     private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss";
-    private static final String TUNER_KEY_ALLOW_MINIMIZE = "pip_allow_minimize";
 
-    private static final int SNAP_STACK_DURATION = 225;
-    private static final int DISMISS_STACK_DURATION = 375;
-    private static final int EXPAND_STACK_DURATION = 225;
-    private static final int MINIMIZE_STACK_MAX_DURATION = 200;
     private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 200;
 
-    // The fraction of the stack width that the user has to drag offscreen to minimize the PIP
-    private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f;
-
     private final Context mContext;
     private final IActivityManager mActivityManager;
     private final IWindowManager mWindowManager;
@@ -86,34 +64,28 @@
     private IPinnedStackController mPinnedStackController;
 
     private PipInputEventReceiver mInputEventReceiver;
-    private PipMenuActivityController mMenuController;
-    private PipDismissViewController mDismissViewController;
+    private final PipMenuActivityController mMenuController;
+    private final PipDismissViewController mDismissViewController;
     private final PipSnapAlgorithm mSnapAlgorithm;
-    private PipMotionHelper mMotionHelper;
 
     // Allow dragging the PIP to a location to close it
     private boolean mEnableDragToDismiss = false;
-    // Allow the PIP to be "docked" slightly offscreen
-    private boolean mEnableMinimizing = true;
 
-    private final Rect mStableInsets = new Rect();
-    private final Rect mPinnedStackBounds = new Rect();
-    private final Rect mBoundedPinnedStackBounds = new Rect();
-    private ValueAnimator mPinnedStackBoundsAnimator = null;
-    private ValueAnimator.AnimatorUpdateListener mUpdatePinnedStackBoundsListener =
-            new AnimatorUpdateListener() {
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            mPinnedStackBounds.set((Rect) animation.getAnimatedValue());
-        }
-    };
+    // The current movement bounds
+    private Rect mMovementBounds = new Rect();
+
+    // The reference bounds used to calculate the normal/expanded target bounds
+    private Rect mNormalBounds = new Rect();
+    private Rect mNormalMovementBounds = new Rect();
+    private Rect mExpandedBounds = new Rect();
+    private Rect mExpandedMovementBounds = new Rect();
 
     private Handler mHandler = new Handler();
     private Runnable mShowDismissAffordance = new Runnable() {
         @Override
         public void run() {
             if (mEnableDragToDismiss) {
-                mDismissViewController.showDismissTarget(mPinnedStackBounds);
+                mDismissViewController.showDismissTarget(mMotionHelper.getBounds());
             }
         }
     };
@@ -121,13 +93,18 @@
     // Behaviour states
     private boolean mIsTappingThrough;
     private boolean mIsMinimized;
+    private boolean mIsMenuVisible;
+    private boolean mIsImeShowing;
+    private int mImeHeight;
+    private float mSavedSnapFraction = -1f;
 
     // Touch state
     private final PipTouchState mTouchState;
     private final FlingAnimationUtils mFlingAnimationUtils;
     private final PipTouchGesture[] mGestures;
+    private final PipMotionHelper mMotionHelper;
 
-    // Temporary vars
+    // Temp vars
     private final Rect mTmpBounds = new Rect();
 
     /**
@@ -160,32 +137,25 @@
     private class PipMenuListener implements PipMenuActivityController.Listener {
         @Override
         public void onPipMenuVisibilityChanged(boolean visible) {
-            if (!visible) {
-                mIsTappingThrough = false;
-                registerInputConsumer();
-            } else {
-                unregisterInputConsumer();
-            }
-            MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU,
-                    visible);
+            setMenuVisibilityState(visible);
         }
 
         @Override
         public void onPipExpand() {
             if (!mIsMinimized) {
-                expandPinnedStackToFullscreen();
+                mMotionHelper.expandPip();
             }
         }
 
         @Override
         public void onPipMinimize() {
-            setMinimizedState(true);
-            animateToClosestMinimizedTarget();
+            setMinimizedStateInternal(true);
+            mMotionHelper.animateToClosestMinimizedState(mMovementBounds, mMenuController);
         }
 
         @Override
         public void onPipDismiss() {
-            BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack);
+            mMotionHelper.dismissPip();
             MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
                     METRIC_VALUE_DISMISSED_BY_TAP);
         }
@@ -208,13 +178,12 @@
         mGestures = new PipTouchGesture[] {
                 mDefaultMovementGesture
         };
-        mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
+        mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mSnapAlgorithm,
+                mFlingAnimationUtils);
         registerInputConsumer();
-        setSnapToEdge(true);
 
         // Register any tuner settings changes
-        Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS,
-                TUNER_KEY_ALLOW_MINIMIZE);
+        Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS);
     }
 
     @Override
@@ -222,17 +191,12 @@
         if (newValue == null) {
             // Reset back to default
             mEnableDragToDismiss = false;
-            mEnableMinimizing = true;
-            setMinimizedState(false);
             return;
         }
         switch (key) {
             case TUNER_KEY_DRAG_TO_DISMISS:
                 mEnableDragToDismiss = Integer.parseInt(newValue) != 0;
                 break;
-            case TUNER_KEY_ALLOW_MINIMIZE:
-                mEnableMinimizing = Integer.parseInt(newValue) != 0;
-                break;
         }
     }
 
@@ -243,26 +207,70 @@
             registerInputConsumer();
         }
         if (mIsMinimized) {
-            setMinimizedState(false);
+            setMinimizedStateInternal(false);
         }
     }
 
     public void onConfigurationChanged() {
-        mSnapAlgorithm.onConfigurationChanged();
-        updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);
+        mMotionHelper.onConfigurationChanged();
+        mMotionHelper.synchronizePinnedStackBounds();
     }
 
-    public void onMinimizedStateChanged(boolean isMinimized) {
-        if (mIsMinimized != isMinimized) {
-            MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
-                    isMinimized);
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        mIsImeShowing = imeVisible;
+        mImeHeight = imeHeight;
+    }
+
+    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+            boolean fromImeAdjustement) {
+        // Re-calculate the expanded bounds
+        mNormalBounds = normalBounds;
+        Rect normalMovementBounds = new Rect();
+        mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds,
+                mIsImeShowing ? mImeHeight : 0);
+        // TODO: Figure out the expanded size policy
+        mExpandedBounds = new Rect(normalBounds);
+        Rect expandedMovementBounds = new Rect();
+        mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
+                mIsImeShowing ? mImeHeight : 0);
+
+
+        // If this is from an IME adjustment, then we should move the PiP so that it is not occluded
+        // by the IME
+        if (fromImeAdjustement) {
+            if (mTouchState.isUserInteracting()) {
+                // Defer the update of the current movement bounds until after the user finishes
+                // touching the screen
+            } else {
+                final Rect bounds = new Rect(mMotionHelper.getBounds());
+                final Rect toMovementBounds = mIsMenuVisible
+                        ? expandedMovementBounds
+                        : normalMovementBounds;
+                if (mIsImeShowing) {
+                    // IME visible
+                    if (bounds.top == mMovementBounds.bottom) {
+                        // If the PIP is currently resting on top of the IME, then adjust it with
+                        // the hiding IME
+                        bounds.offsetTo(bounds.left, toMovementBounds.bottom);
+                    } else {
+                        bounds.offset(0, Math.min(0, toMovementBounds.bottom - bounds.top));
+                    }
+                } else {
+                    // IME hidden
+                    if (bounds.top == mMovementBounds.bottom) {
+                        // If the PIP is resting on top of the IME, then adjust it with the hiding IME
+                        bounds.offsetTo(bounds.left, toMovementBounds.bottom);
+                    }
+                }
+                mMotionHelper.animateToBounds(bounds);
+            }
         }
-        mIsMinimized = isMinimized;
-        mSnapAlgorithm.setMinimized(isMinimized);
-    }
 
-    public void onSnapToEdgeStateChanged(boolean isSnapToEdge) {
-        mSnapAlgorithm.setSnapToEdge(isSnapToEdge);
+        // Update the movement bounds after doing the calculations based on the old movement bounds
+        // above
+        mNormalMovementBounds = normalMovementBounds;
+        mExpandedMovementBounds = expandedMovementBounds;
+        updateMovementBounds();
     }
 
     private boolean handleTouchEvent(MotionEvent ev) {
@@ -276,20 +284,11 @@
 
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
-                // Cancel any existing animations on the pinned stack
-                if (mPinnedStackBoundsAnimator != null) {
-                    mPinnedStackBoundsAnimator.cancel();
-                }
+                mMotionHelper.synchronizePinnedStackBounds();
 
-                updateBoundedPinnedStackBounds(true /* updatePinnedStackBounds */);
                 for (PipTouchGesture gesture : mGestures) {
                     gesture.onDown(mTouchState);
                 }
-                try {
-                    mPinnedStackController.setInInteractiveMode(true);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Could not set dragging state", e);
-                }
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -303,7 +302,7 @@
             case MotionEvent.ACTION_UP: {
                 // Update the movement bounds again if the state has changed since the user started
                 // dragging (ie. when the IME shows)
-                updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);
+                updateMovementBounds();
 
                 for (PipTouchGesture gesture : mGestures) {
                     if (gesture.onUp(mTouchState)) {
@@ -314,11 +313,6 @@
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
-                try {
-                    mPinnedStackController.setInInteractiveMode(false);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Could not set dragging state", e);
-                }
                 break;
             }
         }
@@ -326,16 +320,6 @@
     }
 
     /**
-     * @return whether the current touch state places the pip partially offscreen.
-     */
-    private boolean isDraggingOffscreen(PipTouchState touchState) {
-        PointF lastDelta = touchState.getLastTouchDelta();
-        PointF downDelta = touchState.getDownTouchDelta();
-        float left = mPinnedStackBounds.left + lastDelta.x;
-        return !(mBoundedPinnedStackBounds.left <= left && left <= mBoundedPinnedStackBounds.right);
-    }
-
-    /**
      * Registers the input consumer.
      */
     private void registerInputConsumer() {
@@ -374,27 +358,30 @@
     }
 
     /**
-     * Sets the snap-to-edge state and notifies the controller.
+     * Sets the minimized state.
      */
-    private void setSnapToEdge(boolean snapToEdge) {
-        onSnapToEdgeStateChanged(snapToEdge);
-
-        if (mPinnedStackController != null) {
-            try {
-                mPinnedStackController.setSnapToEdge(snapToEdge);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not set snap mode to edge", e);
-            }
-        }
+    void setMinimizedStateInternal(boolean isMinimized) {
+        setMinimizedState(isMinimized, false /* fromController */);
     }
 
     /**
-     * Sets the minimized state and notifies the controller.
+     * Sets the minimized state.
      */
-    private void setMinimizedState(boolean isMinimized) {
-        onMinimizedStateChanged(isMinimized);
+    void setMinimizedState(boolean isMinimized, boolean fromController) {
+        if (mIsMinimized != isMinimized) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
+                    isMinimized);
+        }
+        mIsMinimized = isMinimized;
+        mSnapAlgorithm.setMinimized(isMinimized);
 
-        if (mPinnedStackController != null) {
+        if (fromController) {
+            if (isMinimized) {
+                // Move the PiP to the new bounds immediately if minimized
+                mMotionHelper.movePip(mMotionHelper.getClosestMinimizedBounds(mNormalBounds,
+                        mMovementBounds));
+            }
+        } else if (mPinnedStackController != null) {
             try {
                 mPinnedStackController.setIsMinimized(isMinimized);
             } catch (RemoteException e) {
@@ -404,178 +391,43 @@
     }
 
     /**
-     * @return whether the given {@param pinnedStackBounds} indicates the PIP should be minimized.
+     * Sets the menu visibility.
      */
-    private boolean shouldMinimizedPinnedStack() {
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
-        if (mPinnedStackBounds.left < 0) {
-            float offscreenFraction = (float) -mPinnedStackBounds.left / mPinnedStackBounds.width();
-            return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
-        } else if (mPinnedStackBounds.right > displaySize.x) {
-            float offscreenFraction = (float) (mPinnedStackBounds.right - displaySize.x) /
-                    mPinnedStackBounds.width();
-            return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
+    void setMenuVisibilityState(boolean isMenuVisible) {
+        if (!isMenuVisible) {
+            mIsTappingThrough = false;
+            registerInputConsumer();
         } else {
-            return false;
+            unregisterInputConsumer();
         }
-    }
+        MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU,
+                isMenuVisible);
 
-    /**
-     * Flings the minimized PIP to the closest minimized snap target.
-     */
-    private void flingToMinimizedSnapTarget(float velocityY) {
-        // We currently only allow flinging the minimized stack up and down, so just lock the
-        // movement bounds to the current stack bounds horizontally
-        Rect movementBounds = new Rect(mPinnedStackBounds.left, mBoundedPinnedStackBounds.top,
-                mPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom);
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mPinnedStackBounds,
-                0 /* velocityX */, velocityY);
-        if (!mPinnedStackBounds.equals(toBounds)) {
-            mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
-                    toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
-            mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
-                    distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
-                    velocityY);
-            mPinnedStackBoundsAnimator.start();
-        }
-    }
-
-    /**
-     * Animates the PIP to the minimized state, slightly offscreen.
-     */
-    private void animateToClosestMinimizedTarget() {
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
-                mPinnedStackBounds);
-        mSnapAlgorithm.applyMinimizedOffset(toBounds, mBoundedPinnedStackBounds, displaySize,
-                mStableInsets);
-        mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
-                toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN,
-                mUpdatePinnedStackBoundsListener);
-        mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mMenuController.hideMenu();
+        if (isMenuVisible != mIsMenuVisible) {
+            if (isMenuVisible) {
+                // Save the current snap fraction and if we do not drag or move the PiP, then
+                // we store back to this snap fraction.  Otherwise, we'll reset the snap
+                // fraction and snap to the closest edge
+                Rect expandedBounds = new Rect(mExpandedBounds);
+                mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
+                        mMovementBounds, mExpandedMovementBounds);
+            } else {
+                // Try and restore the PiP to the closest edge, using the saved snap fraction
+                // if possible
+                Rect normalBounds = new Rect(mNormalBounds);
+                mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
+                        mNormalMovementBounds);
             }
-        });
-        mPinnedStackBoundsAnimator.start();
-    }
-
-    /**
-     * Flings the PIP to the closest snap target.
-     */
-    private Rect flingToSnapTarget(float velocity, float velocityX, float velocityY) {
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
-                mPinnedStackBounds, velocityX, velocityY);
-        if (!mPinnedStackBounds.equals(toBounds)) {
-            mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
-                toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
-            mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
-                distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
-                velocity);
-            mPinnedStackBoundsAnimator.start();
-        }
-        return toBounds;
-    }
-
-    /**
-     * Animates the PIP to the closest snap target.
-     */
-    private Rect animateToClosestSnapTarget() {
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
-                mPinnedStackBounds);
-        if (!mPinnedStackBounds.equals(toBounds)) {
-            mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
-                toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
-            mPinnedStackBoundsAnimator.start();
-        }
-        return toBounds;
-    }
-
-    /**
-     * Animates the dismissal of the PIP over the dismiss target bounds.
-     */
-    private void animateDismissPinnedStack(Rect dismissBounds) {
-        Rect toBounds = new Rect(dismissBounds.centerX(),
-            dismissBounds.centerY(),
-            dismissBounds.centerX() + 1,
-            dismissBounds.centerY() + 1);
-        mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
-            toBounds, DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN, mUpdatePinnedStackBoundsListener);
-        mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack);
-            }
-        });
-        mPinnedStackBoundsAnimator.start();
-    }
-
-    /**
-     * Resizes the pinned stack back to fullscreen.
-     */
-    void expandPinnedStackToFullscreen() {
-        BackgroundThread.getHandler().post(() -> {
-            try {
-                mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
-                        true /* allowResizeInDockedMode */, true /* preserveWindows */,
-                        true /* animate */, EXPAND_STACK_DURATION);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error showing PIP menu activity", e);
-            }
-        });
-    }
-
-    /**
-     * Tries to the move the pinned stack to the given {@param bounds}.
-     */
-    private void movePinnedStack(Rect bounds) {
-        if (!bounds.equals(mPinnedStackBounds)) {
-            mPinnedStackBounds.set(bounds);
-            if (mEnableDragToDismiss) {
-                mDismissViewController.updateDismissTarget(bounds);
-            }
-            mMotionHelper.resizeToBounds(mPinnedStackBounds);
+            mIsMenuVisible = isMenuVisible;
+            updateMovementBounds();
         }
     }
 
     /**
-     * Dismisses the pinned stack.
+     * @return the motion helper.
      */
-    private void dismissPinnedStack() {
-        try {
-            mActivityManager.removeStack(PINNED_STACK_ID);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to remove PIP", e);
-        }
-    }
-
-    /**
-     * Updates the movement bounds of the pinned stack.
-     */
-    private void updateBoundedPinnedStackBounds(boolean updatePinnedStackBounds) {
-        try {
-            StackInfo info = mActivityManager.getStackInfo(PINNED_STACK_ID);
-            if (info != null) {
-                if (updatePinnedStackBounds) {
-                    mPinnedStackBounds.set(info.bounds);
-                }
-                mWindowManager.getStableInsets(info.displayId, mStableInsets);
-                mBoundedPinnedStackBounds.set(mWindowManager.getPictureInPictureMovementBounds(
-                        info.displayId));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not fetch PIP movement bounds.", e);
-        }
-    }
-
-    /**
-     * @return the distance between points {@param p1} and {@param p2}.
-     */
-    private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
-        return PointF.length(r1.left - r2.left, r1.top - r2.top);
+    public PipMotionHelper getMotionHelper() {
+        return mMotionHelper;
     }
 
     /**
@@ -593,25 +445,31 @@
 
         @Override
         boolean onMove(PipTouchState touchState) {
+            if (touchState.startedDragging()) {
+                mSavedSnapFraction = -1f;
+            }
+
             if (touchState.startedDragging() && mEnableDragToDismiss) {
                 mHandler.removeCallbacks(mShowDismissAffordance);
-                mDismissViewController.showDismissTarget(mPinnedStackBounds);
+                mDismissViewController.showDismissTarget(mMotionHelper.getBounds());
             }
 
             if (touchState.isDragging()) {
                 // Move the pinned stack freely
-                PointF lastDelta = touchState.getLastTouchDelta();
-                float left = mPinnedStackBounds.left + lastDelta.x;
-                float top = mPinnedStackBounds.top + lastDelta.y;
+                mTmpBounds.set(mMotionHelper.getBounds());
+                final PointF lastDelta = touchState.getLastTouchDelta();
+                float left = mTmpBounds.left + lastDelta.x;
+                float top = mTmpBounds.top + lastDelta.y;
                 if (!touchState.allowDraggingOffscreen()) {
-                    left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
-                            mBoundedPinnedStackBounds.right, left));
+                    left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
                 }
-                top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
-                        mBoundedPinnedStackBounds.bottom, top));
-                mTmpBounds.set(mPinnedStackBounds);
+                top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top));
                 mTmpBounds.offsetTo((int) left, (int) top);
-                movePinnedStack(mTmpBounds);
+                mMotionHelper.movePip(mTmpBounds);
+
+                if (mEnableDragToDismiss) {
+                    mDismissViewController.updateDismissTarget(mTmpBounds);
+                }
                 return true;
             }
             return false;
@@ -626,9 +484,12 @@
                     final float velocity = PointF.length(vel.x, vel.y);
                     if (touchState.isDragging()
                             && velocity < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-                        if (mDismissViewController.shouldDismiss(mPinnedStackBounds)) {
+                        if (mDismissViewController.shouldDismiss(mMotionHelper.getBounds())) {
                             Rect dismissBounds = mDismissViewController.getDismissBounds();
-                            animateDismissPinnedStack(dismissBounds);
+                            mMotionHelper.animateDismissFromDrag(dismissBounds);
+                            MetricsLogger.action(mContext,
+                                    MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
+                                    METRIC_VALUE_DISMISSED_BY_DRAG);
                             return true;
                         }
                     }
@@ -638,34 +499,34 @@
             }
             if (touchState.isDragging()) {
                 PointF vel = mTouchState.getVelocity();
-                if (!mIsMinimized && (shouldMinimizedPinnedStack()
+                if (!mIsMinimized && (mMotionHelper.shouldMinimizePip()
                         || isHorizontalFlingTowardsCurrentEdge(vel))) {
                     // Pip should be minimized
-                    setMinimizedState(true);
-                    animateToClosestMinimizedTarget();
+                    setMinimizedStateInternal(true);
+                    mMotionHelper.animateToClosestMinimizedState(mMovementBounds, mMenuController);
                     return true;
                 }
                 if (mIsMinimized) {
                     // If we're dragging and it wasn't a minimize gesture
                     // then we shouldn't be minimized.
-                    setMinimizedState(false);
+                    setMinimizedStateInternal(false);
                 }
 
                 final float velocity = PointF.length(vel.x, vel.y);
                 if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-                    flingToSnapTarget(velocity, vel.x, vel.y);
+                    mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds);
                 } else {
-                    animateToClosestSnapTarget();
+                    mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
                 }
             } else if (mIsMinimized) {
                 // This was a tap, so no longer minimized
-                animateToClosestSnapTarget();
-                setMinimizedState(false);
+                mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
+                setMinimizedStateInternal(false);
             } else if (!mIsTappingThrough) {
                 mMenuController.showMenu();
                 mIsTappingThrough = true;
             } else {
-                expandPinnedStackToFullscreen();
+                mMotionHelper.expandPip();
             }
             return true;
         }
@@ -679,17 +540,30 @@
         final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
         final boolean isFling = PointF.length(vel.x, vel.y) > mFlingAnimationUtils
                 .getMinVelocityPxPerSecond();
-        final boolean towardsCurrentEdge = onEdge(true /* left */) && vel.x < 0
-                || onEdge(false /* right */) && vel.x > 0;
+        final boolean towardsCurrentEdge = isOverEdge(true /* left */) && vel.x < 0
+                || isOverEdge(false /* right */) && vel.x > 0;
         return towardsCurrentEdge && isHorizontal && isFling;
     }
 
-    private boolean onEdge(boolean checkLeft) {
+    /**
+     * @return whether the given bounds are on the left or right edge (depending on
+     *         {@param checkLeft})
+     */
+    private boolean isOverEdge(boolean checkLeft) {
+        final Rect bounds = mMotionHelper.getBounds();
         if (checkLeft) {
-            return mPinnedStackBounds.left <= mBoundedPinnedStackBounds.left;
+            return bounds.left <= mMovementBounds.left;
         } else {
-            return mPinnedStackBounds.right >= mBoundedPinnedStackBounds.right
-                    + mPinnedStackBounds.width();
+            return bounds.right >= mMovementBounds.right + bounds.width();
         }
     }
+
+    /**
+     * Updates the current movement bounds based on whether the menu is currently visible.
+     */
+    private void updateMovementBounds() {
+        mMovementBounds = mIsMenuVisible
+                ? mExpandedMovementBounds
+                : mNormalMovementBounds;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 2e84ced..868b34b7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -34,6 +34,7 @@
     private final PointF mLastTouch = new PointF();
     private final PointF mLastDelta = new PointF();
     private final PointF mVelocity = new PointF();
+    private boolean mIsUserInteracting = false;
     private boolean mIsDragging = false;
     private boolean mStartedDragging = false;
     private boolean mAllowDraggingOffscreen = false;
@@ -57,6 +58,7 @@
                 mIsDragging = false;
                 mStartedDragging = false;
                 mAllowDraggingOffscreen = true;
+                mIsUserInteracting = true;
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -107,6 +109,7 @@
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
+                mIsUserInteracting = false;
                 recycleVelocityTracker();
                 break;
             }
@@ -151,6 +154,13 @@
     }
 
     /**
+     * @return whether the user is currently interacting with the PiP.
+     */
+    public boolean isUserInteracting() {
+        return mIsUserInteracting;
+    }
+
+    /**
      * @return whether the user has started dragging just in the last handled touch event.
      */
     public boolean startedDragging() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 964fefa..cf7b05e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.media.session.MediaController;
@@ -39,6 +40,8 @@
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -51,6 +54,8 @@
 import java.util.List;
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
 
 /**
@@ -159,6 +164,8 @@
     private boolean mOnboardingShown;
     private String[] mLastPackagesResourceGranted;
 
+    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
         public void run() {
@@ -196,6 +203,32 @@
                 }
             };
 
+    /**
+     * Handler for messages from the PIP controller.
+     */
+    private class PinnedStackListener extends IPinnedStackListener.Stub {
+
+        @Override
+        public void onListenerRegistered(IPinnedStackController controller) {}
+
+        @Override
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+
+        @Override
+        public void onMinimizedStateChanged(boolean isMinimized) {}
+
+        @Override
+        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+                boolean fromImeAdjustement) {
+            mHandler.post(() -> {
+                mDefaultPipBounds.set(normalBounds);
+            });
+        }
+
+        @Override
+        public void onActionsChanged(ParceledListSlice actions) {}
+    }
+
     private PipManager() { }
 
     /**
@@ -221,16 +254,16 @@
         mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
         mMediaSessionManager =
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+
+        try {
+            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register pinned stack listener", e);
+        }
     }
 
     private void loadConfigurationsAndApply() {
         Resources res = mContext.getResources();
-        try {
-            mDefaultPipBounds = mWindowManager.getPictureInPictureDefaultBounds(
-                    Display.DEFAULT_DISPLAY);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get default PIP bounds", e);
-        }
         mSettingsPipBounds = Rect.unflattenFromString(res.getString(
                 R.string.pip_settings_bounds));
         mMenuModePipBounds = Rect.unflattenFromString(res.getString(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d9ef02..aae5dd8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2932,7 +2932,7 @@
         public boolean findAccessibilityNodeInfoByAccessibilityId(
                 int accessibilityWindowId, long accessibilityNodeId, int interactionId,
                 IAccessibilityInteractionConnectionCallback callback, int flags,
-                long interrogatingTid) throws RemoteException {
+                long interrogatingTid, Bundle arguments) throws RemoteException {
             final int resolvedWindowId;
             IAccessibilityInteractionConnection connection = null;
             Region partialInteractiveRegion = Region.obtain();
@@ -2964,7 +2964,7 @@
             try {
                 connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
                         partialInteractiveRegion, interactionId, callback, mFetchFlags | flags,
-                        interrogatingPid, interrogatingTid, spec);
+                        interrogatingPid, interrogatingTid, spec, arguments);
                 return true;
             } catch (RemoteException re) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ae76b61..93fb911 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1624,10 +1624,6 @@
     int mThumbnailHeight;
     float mFullscreenThumbnailScale;
 
-    /** The aspect ratio bounds of the PIP. */
-    float mMinPipAspectRatio;
-    float mMaxPipAspectRatio;
-
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
     final UiHandler mUiHandler;
@@ -7662,12 +7658,13 @@
                     r.pictureInPictureArgs.copyOnlySet(args);
                     final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
                     final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
-                    final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
-                            ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio)
-                            : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
+                    final Rect bounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
+                            aspectRatio);
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
                             bounds, true /* moveHomeStackToFront */);
-                    mStackSupervisor.getStack(PINNED_STACK_ID).setPictureInPictureActions(actions);
+                    final ActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
+                    stack.setPictureInPictureAspectRatio(aspectRatio);
+                    stack.setPictureInPictureActions(actions);
 
                     MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED,
                             r.supportsPictureInPictureWhilePausing);
@@ -7744,10 +7741,6 @@
         }
     }
 
-    private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
-        return mMinPipAspectRatio <= aspectRatio && aspectRatio <= mMaxPipAspectRatio;
-    }
-
     /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
@@ -7778,10 +7771,15 @@
         }
 
         if (args.hasSetAspectRatio()
-                && !isValidPictureInPictureAspectRatio(args.getAspectRatio())) {
+                && !mWindowManager.isValidPictureInPictureAspectRatio(r.getStack().mDisplayId,
+                        args.getAspectRatio())) {
+            final float minAspectRatio = mContext.getResources().getFloat(
+                    com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
+            final float maxAspectRatio = mContext.getResources().getFloat(
+                    com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
             throw new IllegalArgumentException(String.format(caller
                     + ": Aspect ratio is too extreme (must be between %f and %f).",
-                            mMinPipAspectRatio, mMaxPipAspectRatio));
+                            minAspectRatio, maxAspectRatio));
         }
 
         if (args.hasSetActions()
@@ -13464,10 +13462,6 @@
                     com.android.internal.R.dimen.thumbnail_width);
             mThumbnailHeight = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.thumbnail_height);
-            mMinPipAspectRatio = res.getFloat(
-                    com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
-            mMaxPipAspectRatio = res.getFloat(
-                    com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
             mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
                     com.android.internal.R.string.config_appsNotReportingCrashes));
             mUserController.mUserSwitchUiEnabled = !res.getBoolean(
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ba25120..104fc6a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -544,6 +544,10 @@
         mWindowContainerController.setPictureInPictureAspectRatio(aspectRatio);
     }
 
+    void setPictureInPictureActions(List<RemoteAction> actions) {
+        mWindowContainerController.setPictureInPictureActions(actions);
+    }
+
     void getStackDockedModeBounds(Rect outBounds, Rect outTempBounds, Rect outTempInsetBounds,
             boolean ignoreVisibility) {
         mWindowContainerController.getStackDockedModeBounds(outBounds, outTempBounds,
@@ -554,10 +558,6 @@
         mWindowContainerController.prepareFreezingTaskBounds();
     }
 
-    void setPictureInPictureActions(List<RemoteAction> actions) {
-        mWindowContainerController.setPictureInPictureActions(actions);
-    }
-
     void getWindowContainerBounds(Rect outBounds) {
         if (mWindowContainerController != null) {
             mWindowContainerController.getBounds(outBounds);
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 596c3d8..a872ea4 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -22,7 +22,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
-import android.animation.ValueAnimator;
 import android.app.RemoteAction;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
@@ -42,7 +41,6 @@
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
-import com.android.internal.policy.PipMotionHelper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.server.UiThread;
 
@@ -51,7 +49,20 @@
 import java.util.List;
 
 /**
- * Holds the common state of the pinned stack between the system and SystemUI.
+ * Holds the common state of the pinned stack between the system and SystemUI. If SystemUI ever
+ * needs to be restarted, it will be notified with the last known state.
+ *
+ * Changes to the pinned stack also flow through this controller, and generally, the system only
+ * changes the pinned stack bounds through this controller in two ways:
+ *
+ * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio
+ *    and IME state into account.
+ * 2) When rotating the device: the controller calculates the new bounds in the new orientation,
+ *    taking the minimized and IME state into account. In this case, we currently ignore the
+ *    SystemUI adjustments (ie. expanded for menu, interaction, etc).
+ *
+ * Other changes in the system, including adjustment of IME, configuration change, and more are
+ * handled by SystemUI (similar to the docked stack divider).
  */
 class PinnedStackController {
 
@@ -67,18 +78,15 @@
 
     private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
     private final PipSnapAlgorithm mSnapAlgorithm;
-    private final PipMotionHelper mMotionHelper;
 
     // States that affect how the PIP can be manipulated
-    private boolean mInInteractiveMode;
     private boolean mIsMinimized;
-    private boolean mIsSnappingToEdge;
     private boolean mIsImeShowing;
     private int mImeHeight;
-    private ValueAnimator mBoundsAnimator = null;
 
-    // The set of actions that are currently allowed on the PiP activity
+    // The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
     private ArrayList<RemoteAction> mActions = new ArrayList<>();
+    private float mAspectRatio = -1f;
 
     // Used to calculate stack bounds across rotations
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
@@ -89,6 +97,10 @@
     private Size mDefaultStackSize;
     private Point mScreenEdgeInsets;
 
+    // The aspect ratio bounds of the PIP.
+    private float mMinAspectRatio;
+    private float mMaxAspectRatio;
+
     // Temp vars for calculation
     private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
     private final Rect mTmpInsets = new Rect();
@@ -100,31 +112,12 @@
     private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
 
         @Override
-        public void setInInteractiveMode(final boolean inInteractiveMode) {
-            mHandler.post(() -> {
-                // Cancel any existing animations on the PIP once the user starts dragging it
-                if (mBoundsAnimator != null && inInteractiveMode) {
-                    mBoundsAnimator.cancel();
-                }
-                mInInteractiveMode = inInteractiveMode;
-            });
-        }
-
-        @Override
         public void setIsMinimized(final boolean isMinimized) {
             mHandler.post(() -> {
                 mIsMinimized = isMinimized;
                 mSnapAlgorithm.setMinimized(isMinimized);
             });
         }
-
-        @Override
-        public void setSnapToEdge(final boolean snapToEdge) {
-            mHandler.post(() -> {
-                mIsSnappingToEdge = snapToEdge;
-                mSnapAlgorithm.setSnapToEdge(snapToEdge);
-            });
-        }
     }
 
     /**
@@ -135,7 +128,6 @@
         @Override
         public void binderDied() {
             // Clean up the state if the listener dies
-            mInInteractiveMode = false;
             mPinnedStackListener = null;
         }
     }
@@ -144,13 +136,13 @@
         mService = service;
         mDisplayContent = displayContent;
         mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
-        mMotionHelper = new PipMotionHelper(UiThread.getHandler());
         mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
         reloadResources();
     }
 
     void onConfigurationChanged() {
         reloadResources();
+        notifyMovementBoundsChanged(false /* fromImeAdjustment */);
     }
 
     /**
@@ -169,6 +161,10 @@
                 dpToPx(defaultSizeDp.getHeight(), mTmpMetrics));
         mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
                 dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
+        mMinAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
+        mMaxAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
     }
 
     /**
@@ -179,20 +175,29 @@
             listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
             listener.onListenerRegistered(mCallbacks);
             mPinnedStackListener = listener;
-            notifyBoundsChanged(mIsImeShowing);
-            notifyMinimizeChanged(mIsMinimized);
-            notifySnapToEdgeChanged(mIsSnappingToEdge);
+            notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
+            // The movement bounds notification needs to be sent before the minimized state, since
+            // SystemUI may use the bounds to retore the minimized position
+            notifyMovementBoundsChanged(false /* fromImeAdjustment */);
             notifyActionsChanged(mActions);
+            notifyMinimizeChanged(mIsMinimized);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
     }
 
     /**
+     * @return whether the given {@param aspectRatio} is valid.
+     */
+    public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+        return mMinAspectRatio <= aspectRatio && aspectRatio <= mMaxAspectRatio;
+    }
+
+    /**
      * Returns the current bounds (or the default bounds if there are no current bounds) with the
      * specified aspect ratio.
      */
-    Rect getAspectRatioBounds(Rect stackBounds, float aspectRatio) {
+    Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio) {
         // Save the snap fraction, calculate the aspect ratio based on the current bounds
         final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
                 getMovementBounds(stackBounds));
@@ -236,27 +241,20 @@
         final Rect movementBounds = new Rect();
         getInsetBounds(movementBounds);
 
-        // Adjust the right/bottom to ensure the stack bounds never goes offscreen
-        movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
-                stackBounds.width());
-        movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
-                stackBounds.height());
-
         // Apply the movement bounds adjustments based on the current state
-        if (adjustForIme) {
-            if (mIsImeShowing) {
-                movementBounds.bottom -= mImeHeight;
-            }
-        }
+        mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
+                (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
         return movementBounds;
     }
 
     /**
+     * @param preChangeTargetBounds The final bounds of the stack if it is currently animating
      * @return the repositioned PIP bounds given it's pre-change bounds, and the new display
      *         content.
      */
-    Rect onDisplayChanged(Rect preChangeStackBounds, DisplayContent displayContent) {
-        final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
+    Rect onDisplayChanged(Rect preChangeStackBounds, Rect preChangeTargetBounds,
+            DisplayContent displayContent) {
+        final Rect postChangeStackBounds = new Rect(preChangeTargetBounds);
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         if (!mDisplayInfo.equals(displayInfo)) {
             // Calculate the snap fraction of the current stack along the old movement bounds, and
@@ -277,6 +275,7 @@
                 mSnapAlgorithm.applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds,
                         displaySize, mStableInsets);
             }
+            notifyMovementBoundsChanged(false /* fromImeAdjustment */);
         }
         return postChangeStackBounds;
     }
@@ -290,42 +289,19 @@
             return;
         }
 
-        final Rect stackBounds = new Rect();
-        mService.getStackBounds(PINNED_STACK_ID, stackBounds);
-        final Rect prevMovementBounds = getMovementBounds(stackBounds);
         mIsImeShowing = adjustedForIme;
         mImeHeight = imeHeight;
-        if (mInInteractiveMode) {
-            // If the user is currently interacting with the PIP and the ime state changes, then
-            // don't adjust the bounds and defer that to after the interaction
-            notifyBoundsChanged(adjustedForIme /* adjustedForIme */);
-        } else {
-            // Otherwise, we can move the PIP to a sane location to ensure that it does not block
-            // the user from interacting with the IME
-            final Rect movementBounds = getMovementBounds(stackBounds);
-            final Rect toBounds = new Rect(stackBounds);
-            if (adjustedForIme) {
-                // IME visible
-                if (stackBounds.top == prevMovementBounds.bottom) {
-                    // If the PIP is resting on top of the IME, then adjust it with the hiding IME
-                    toBounds.offsetTo(toBounds.left, movementBounds.bottom);
-                } else {
-                    toBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
-                }
-            } else {
-                // IME hidden
-                if (stackBounds.top == prevMovementBounds.bottom) {
-                    // If the PIP is resting on top of the IME, then adjust it with the hiding IME
-                    toBounds.offsetTo(toBounds.left, movementBounds.bottom);
-                }
-            }
-            if (!toBounds.equals(stackBounds)) {
-                if (mBoundsAnimator != null) {
-                    mBoundsAnimator.cancel();
-                }
-                mBoundsAnimator = mMotionHelper.createAnimationToBounds(stackBounds, toBounds);
-                mBoundsAnimator.start();
-            }
+        notifyImeVisibilityChanged(adjustedForIme, imeHeight);
+        notifyMovementBoundsChanged(true /* fromImeAdjustment */);
+    }
+
+    /**
+     * Sets the current aspect ratio.
+     */
+    void setAspectRatio(float aspectRatio) {
+        if (Float.compare(mAspectRatio, aspectRatio) != 0) {
+            mAspectRatio = aspectRatio;
+            notifyMovementBoundsChanged(false /* fromImeAdjustment */);
         }
     }
 
@@ -341,12 +317,12 @@
     }
 
     /**
-     * Notifies listeners that the PIP movement bounds have changed.
+     * Notifies listeners that the PIP needs to be adjusted for the IME.
      */
-    private void notifyBoundsChanged(boolean adjustedForIme) {
+    private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
         if (mPinnedStackListener != null) {
             try {
-                mPinnedStackListener.onBoundsChanged(adjustedForIme);
+                mPinnedStackListener.onImeVisibilityChanged(imeVisible, imeHeight);
             } catch (RemoteException e) {
                 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
             }
@@ -367,19 +343,6 @@
     }
 
     /**
-     * Notifies listeners that the PIP snap-to-edge state has changed.
-     */
-    private void notifySnapToEdgeChanged(boolean isSnappingToEdge) {
-        if (mPinnedStackListener != null) {
-            try {
-                mPinnedStackListener.onSnapToEdgeStateChanged(isSnappingToEdge);
-            } catch (RemoteException e) {
-                Slog.e(TAG_WM, "Error delivering snap-to-edge changed event.", e);
-            }
-        }
-    }
-
-    /**
      * Notifies listeners that the PIP actions have changed.
      */
     private void notifyActionsChanged(List<RemoteAction> actions) {
@@ -393,6 +356,26 @@
     }
 
     /**
+     * Notifies listeners that the PIP movement bounds have changed.
+     */
+    private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
+        if (mPinnedStackListener != null) {
+            try {
+                Rect insetBounds = new Rect();
+                getInsetBounds(insetBounds);
+                Rect normalBounds = getDefaultBounds();
+                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+                    transformBoundsToAspectRatio(normalBounds, mAspectRatio);
+                }
+                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+                        fromImeAdjustement);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+            }
+        }
+    }
+
+    /**
      * @return the bounds on the screen that the PIP can be visible in.
      */
     private void getInsetBounds(Rect outRect) {
@@ -418,7 +401,6 @@
         pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
         pw.println();
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
-        pw.println(prefix + "  mInInteractiveMode=" + mInInteractiveMode);
         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
         if (mActions.isEmpty()) {
             pw.println(prefix + "  mActions=[]");
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index e2ea2c5..36d07e0 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -216,7 +216,28 @@
 
             final int displayId = mContainer.getDisplayContent().getDisplayId();
             final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
-            animateResizePinnedStack(toBounds, -1 /* duration */);
+            final Rect targetBounds = new Rect();
+            mContainer.getAnimatingBounds(targetBounds);
+            if (!toBounds.equals(targetBounds)) {
+                animateResizePinnedStack(toBounds, -1 /* duration */);
+            }
+
+            final PinnedStackController pinnedStackController =
+                    mContainer.getDisplayContent().getPinnedStackController();
+            pinnedStackController.setAspectRatio(
+                    pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
+                            ? aspectRatio : -1f);
+        }
+    }
+
+    /** Sets the current picture-in-picture actions. */
+    public void setPictureInPictureActions(List<RemoteAction> actions) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
         }
     }
 
@@ -244,17 +265,6 @@
         }
     }
 
-    /** Sets the current picture-in-picture actions. */
-    public void setPictureInPictureActions(List<RemoteAction> actions) {
-        synchronized (mWindowMap) {
-            if (!mService.mSupportsPictureInPicture || mContainer == null) {
-                return;
-            }
-
-            mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
-        }
-    }
-
     private void getRawBounds(Rect outBounds) {
         if (mContainer.getRawFullscreen()) {
             outBounds.setEmpty();
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a1c9c29..544d1e3 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -393,8 +393,10 @@
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         switch (mStackId) {
             case PINNED_STACK_ID:
+                Rect targetBounds = new Rect();
+                getAnimatingBounds(targetBounds);
                 mTmpRect2 = mDisplayContent.getPinnedStackController().onDisplayChanged(mBounds,
-                        mDisplayContent);
+                        targetBounds, mDisplayContent);
                 break;
             case DOCKED_STACK_ID:
                 repositionDockedStackAfterRotation(mTmpRect2);
@@ -670,7 +672,9 @@
 
         // Update the pinned stack controller after the display info is updated
         if (mStackId == PINNED_STACK_ID) {
-            mDisplayContent.getPinnedStackController().onDisplayChanged(oldBounds,
+            Rect targetBounds = new Rect();
+            getAnimatingBounds(targetBounds);
+            mDisplayContent.getPinnedStackController().onDisplayChanged(oldBounds, targetBounds,
                     mDisplayContent);
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 971794b..597e8b6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2829,36 +2829,6 @@
         mDockedStackCreateBounds = bounds;
     }
 
-    @Override
-    public Rect getPictureInPictureDefaultBounds(int displayId) {
-        synchronized (mWindowMap) {
-            if (!mSupportsPictureInPicture) {
-                return null;
-            }
-
-            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            return displayContent.getPinnedStackController().getDefaultBounds();
-        }
-    }
-
-    @Override
-    public Rect getPictureInPictureMovementBounds(int displayId) {
-        synchronized (mWindowMap) {
-            if (!mSupportsPictureInPicture) {
-                return null;
-            }
-
-            final Rect stackBounds = new Rect();
-            getStackBounds(PINNED_STACK_ID, stackBounds);
-            if (stackBounds.isEmpty()) {
-                return stackBounds;
-            }
-
-            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            return displayContent.getPinnedStackController().getMovementBounds(stackBounds);
-        }
-    }
-
     public Rect getPictureInPictureBounds(int displayId, float aspectRatio) {
         synchronized (mWindowMap) {
             if (!mSupportsPictureInPicture) {
@@ -2871,6 +2841,8 @@
                 return null;
             }
 
+            final PinnedStackController pinnedStackController =
+                    displayContent.getPinnedStackController();
             final TaskStack stack = displayContent.getStackById(PINNED_STACK_ID);
             if (stack != null) {
                 // If the stack exists, then use its final bounds to calculate the new aspect ratio
@@ -2879,13 +2851,23 @@
                 stack.getAnimatingBounds(stackBounds);
             } else {
                 // Otherwise, just calculate the aspect ratio bounds from the default bounds
-                stackBounds = displayContent.getPinnedStackController().getDefaultBounds();
+                stackBounds = pinnedStackController.getDefaultBounds();
             }
-            return displayContent.getPinnedStackController().getAspectRatioBounds(stackBounds,
-                    aspectRatio);
+
+            if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
+                return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio);
+            } else {
+                return stackBounds;
+            }
         }
     }
 
+    public boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
+        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+        return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio(
+                aspectRatio);
+    }
+
     @Override
     public void getStackBounds(int stackId, Rect bounds) {
         synchronized (mWindowMap) {
diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp
index 517fce0..05ef0d1 100644
--- a/services/core/jni/com_android_server_location_ContextHubService.cpp
+++ b/services/core/jni/com_android_server_location_ContextHubService.cpp
@@ -558,12 +558,12 @@
 void initContextHubService() {
     db.hubInfo.numHubs = 0;
 
-    db.hubInfo.contextHub = IContexthub::getService("context_hub_hal");
+    db.hubInfo.contextHub = IContexthub::getService("context_hub");
 
     if (db.hubInfo.contextHub == nullptr) {
         ALOGE("Could not load context hub hal");
     } else {
-        ALOGI("Loaded context hub hal");
+        ALOGI("Loaded context hub hal, isRemote %s", db.hubInfo.contextHub->isRemote() ? "TRUE" : "FALSE");
     }
 
     // Prep for storing app info
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 56898f1..253ea6f 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -483,16 +483,6 @@
     }
 
     @Override
-    public Rect getPictureInPictureDefaultBounds(int displayId) {
-        return null;
-    }
-
-    @Override
-    public Rect getPictureInPictureMovementBounds(int displayId)  {
-        return null;
-    }
-
-    @Override
     public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
             throws RemoteException {
     }