Merge "Internal cleanup of window manager"
diff --git a/api/current.txt b/api/current.txt
index eeaabf6..8522034 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -53041,6 +53041,7 @@
     method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
     method @NonNull public final android.view.ViewStructure newViewStructure(@NonNull android.view.View);
     method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
+    method public final void notifySessionLifecycle(boolean);
     method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
     method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
     method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence);
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 10ac4a1..476fae3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -358,9 +358,10 @@
 
 bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
                    const LogEvent& event) {
-    if (simpleMatcher.field_value_matcher_size() <= 0) {
-        return event.GetTagId() == simpleMatcher.atom_id();
+    if (event.GetTagId() != simpleMatcher.atom_id()) {
+        return false;
     }
+
     for (const auto& matcher : simpleMatcher.field_value_matcher()) {
         if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
             return false;
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 70f0f6f..441d3c8 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -330,6 +330,7 @@
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Tag found in kAtomsWithUidField and has matching uid
+    simpleMatcher->set_atom_id(TAG_ID_2);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
     // Tag found in kAtomsWithUidField but has non-matching uid
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c74daa8..3933e81 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -519,9 +519,9 @@
      * <li>The app is a system app.</li>
      * <li>The app doesn't request any <a href="/guide/topics/permissions/overview">permissions</a>.
      * </li>
-     * <li>The <code>&lt;application&gt;</code> tag in the app's manifest doesn't contain any child
-     * elements that represent
-     * <a href="/guide/components/fundamentals#DeclaringComponents">app components</a>.</li>
+     * <li>The app doesn't have a <em>launcher activity</em> that is enabled by default. A launcher
+     * activity has an intent containing the <code>ACTION_MAIN</code> action and the
+     * <code>CATEGORY_LAUNCHER</code> category.</li>
      * </ul>
      *
      * <p>Additionally, the system hides synthesized activities for some or all apps in the
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 30e5959..76e728a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -182,6 +182,12 @@
      */
     public static final int NETWORK_STACK_UID = 1073;
 
+    /**
+     * Defines the UID/GID for fs-verity certificate ownership in keystore.
+     * @hide
+     */
+    public static final int FSVERITY_CERT_UID = 1075;
+
     /** {@hide} */
     public static final int NOBODY_UID = 9999;
 
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 8665106..1c50d73 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -276,6 +276,7 @@
         final int runCount = mDirections.getRunCount();
         for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -360,6 +361,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -417,6 +419,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index d2dcb568..f1d152b 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -16,6 +16,8 @@
 
 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.
@@ -30,15 +32,17 @@
     oneway void setIsMinimized(boolean isMinimized);
 
     /**
-     * Notifies the controller of the current min edge size, this is needed to allow the system to
-     * properly calculate the aspect ratio of the expanded PIP.  The given {@param minEdgeSize} is
-     * always bounded to be larger than the default minEdgeSize, so the caller can call this method
-     * with 0 to reset to the default size.
-     */
-    oneway void setMinEdgeSize(int minEdgeSize);
-
-    /**
      * @return what WM considers to be the current device rotation.
      */
     int getDisplayRotation();
+
+    /**
+     * Notifies the controller to actually start the PiP animation.
+     * The bounds would be calculated based on the last save reentry fraction internally.
+     * {@param destinationBounds} is the stack bounds of the final PiP window
+     * and {@param sourceRectHint} is the source bounds hint used when entering picture-in-picture,
+     * expect the same bound passed via IPinnedStackListener#onPrepareAnimation.
+     * {@param animationDuration} suggests the animation duration transitioning to PiP window.
+     */
+    void startAnimation(in Rect destinationBounds, in Rect sourceRectHint, int animationDuration);
 }
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 2da353b..806d81e 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -16,8 +16,10 @@
 
 package android.view;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 
 /**
@@ -36,18 +38,13 @@
     /**
      * 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.  The {@param animatingBounds} are provided
-     * to indicate the current target bounds of the pinned stack (the final bounds if animating,
-     * the current bounds if not), which may be helpful in calculating dependent animation bounds.
-     *
-     * The {@param displayRotation} is provided so that the client can verify when making certain
-     * calls that it will not provide stale information based on an old display rotation (ie. if
-     * the WM has changed in the mean time but the client has not received onMovementBoundsChanged).
+     * the components that allow the listener to calculate the movement bounds itself.
+     * The {@param animatingBounds} are provided to indicate the current target bounds of the
+     * pinned stack (the final bounds if animating, the current bounds if not),
+     * which may be helpful in calculating dependent animation bounds.
      */
-    void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation);
+    void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment);
 
     /**
      * Called when window manager decides to adjust the pinned stack bounds because of the IME, or
@@ -76,4 +73,47 @@
      * is first registered to allow the listener to synchronized its state with the controller.
      */
     void onActionsChanged(in ParceledListSlice actions);
+
+    /**
+     * Called by the window manager to notify the listener to save the reentry fraction,
+     * typically when an Activity leaves PiP (picture-in-picture) mode to fullscreen.
+     * {@param componentName} represents the application component of PiP window
+     * while {@param bounds} is the current PiP bounds used to calculate the
+     * reentry snap fraction.
+     */
+    void onSaveReentrySnapFraction(in ComponentName componentName, in Rect bounds);
+
+    /**
+     * Called by the window manager to notify the listener to reset saved reentry fraction,
+     * typically when an Activity enters PiP (picture-in-picture) mode from fullscreen.
+     * {@param componentName} represents the application component of PiP window.
+     */
+    void onResetReentrySnapFraction(in ComponentName componentName);
+
+    /**
+     * Called when the window manager has detected change on DisplayInfo,  or
+     * when the listener is first registered to allow the listener to synchronized its state with
+     * the controller.
+     */
+    void onDisplayInfoChanged(in DisplayInfo displayInfo);
+
+    /**
+     * Called by the window manager at the beginning of a configuration update cascade
+     * since the metrics from these resources are used for bounds calculations.
+     */
+    void onConfigurationChanged();
+
+    /**
+     * Called by the window manager when the aspect ratio is reset.
+     */
+    void onAspectRatioChanged(float aspectRatio);
+
+    /**
+     * Called by the window manager to notify the listener to prepare for PiP animation.
+     * Internally, the target bounds would be calculated from the given {@param aspectRatio}
+     * and {@param bounds}, the saved reentry snap fraction also contributes.
+     * Caller would wait for a IPinnedStackController#startAnimation callback to actually
+     * start the animation, see details in IPinnedStackController.
+     */
+    void onPrepareAnimation(in Rect sourceRectHint, float aspectRatio, in Rect bounds);
 }
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 85457cb..1b6c575 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -30,6 +30,14 @@
  */
 public class SyncRtSurfaceTransactionApplier {
 
+    public static final int FLAG_ALL = 0xffffffff;
+    public static final int FLAG_ALPHA = 1;
+    public static final int FLAG_MATRIX = 1 << 1;
+    public static final int FLAG_WINDOW_CROP = 1 << 2;
+    public static final int FLAG_LAYER = 1 << 3;
+    public static final int FLAG_CORNER_RADIUS = 1 << 4;
+    public static final int FLAG_VISIBILITY = 1 << 5;
+
     private final Surface mTargetSurface;
     private final ViewRootImpl mTargetViewRootImpl;
     private final float[] mTmpFloat9 = new float[9];
@@ -72,15 +80,27 @@
     }
 
     public static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) {
-        t.setMatrix(params.surface, params.matrix, tmpFloat9);
-        t.setWindowCrop(params.surface, params.windowCrop);
-        t.setAlpha(params.surface, params.alpha);
-        t.setLayer(params.surface, params.layer);
-        t.setCornerRadius(params.surface, params.cornerRadius);
-        if (params.visible) {
-            t.show(params.surface);
-        } else {
-            t.hide(params.surface);
+        if ((params.flags & FLAG_MATRIX) != 0) {
+            t.setMatrix(params.surface, params.matrix, tmpFloat9);
+        }
+        if ((params.flags & FLAG_WINDOW_CROP) != 0) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
+        if ((params.flags & FLAG_ALPHA) != 0) {
+            t.setAlpha(params.surface, params.alpha);
+        }
+        if ((params.flags & FLAG_LAYER) != 0) {
+            t.setLayer(params.surface, params.layer);
+        }
+        if ((params.flags & FLAG_CORNER_RADIUS) != 0) {
+            t.setCornerRadius(params.surface, params.cornerRadius);
+        }
+        if ((params.flags & FLAG_VISIBILITY) != 0) {
+            if (params.visible) {
+                t.show(params.surface);
+            } else {
+                t.hide(params.surface);
+            }
         }
     }
 
@@ -115,17 +135,95 @@
 
     public static class SurfaceParams {
 
-        /**
-         * Constructs surface parameters to be applied when the current view state gets pushed to
-         * RenderThread.
-         *
-         * @param surface The surface to modify.
-         * @param alpha Alpha to apply.
-         * @param matrix Matrix to apply.
-         * @param windowCrop Crop to apply.
-         */
-        public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+        public static class Builder {
+            final SurfaceControl surface;
+            int flags;
+            float alpha;
+            float cornerRadius;
+            Matrix matrix;
+            Rect windowCrop;
+            int layer;
+            boolean visible;
+
+            /**
+             * @param surface The surface to modify.
+             */
+            public Builder(SurfaceControl surface) {
+                this.surface = surface;
+            }
+
+            /**
+             * @param alpha The alpha value to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withAlpha(float alpha) {
+                this.alpha = alpha;
+                flags |= FLAG_ALPHA;
+                return this;
+            }
+
+            /**
+             * @param matrix The matrix to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withMatrix(Matrix matrix) {
+                this.matrix = matrix;
+                flags |= FLAG_MATRIX;
+                return this;
+            }
+
+            /**
+             * @param windowCrop The window crop to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withWindowCrop(Rect windowCrop) {
+                this.windowCrop = windowCrop;
+                flags |= FLAG_WINDOW_CROP;
+                return this;
+            }
+
+            /**
+             * @param layer The layer to assign the surface.
+             * @return this Builder
+             */
+            public Builder withLayer(int layer) {
+                this.layer = layer;
+                flags |= FLAG_LAYER;
+                return this;
+            }
+
+            /**
+             * @param radius the Radius for rounded corners to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withCornerRadius(float radius) {
+                this.cornerRadius = radius;
+                flags |= FLAG_CORNER_RADIUS;
+                return this;
+            }
+
+            /**
+             * @param visible The visibility to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withVisibility(boolean visible) {
+                this.visible = visible;
+                flags |= FLAG_VISIBILITY;
+                return this;
+            }
+
+            /**
+             * @return a new SurfaceParams instance
+             */
+            public SurfaceParams build() {
+                return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
+                        cornerRadius, visible);
+            }
+        }
+
+        private SurfaceParams(SurfaceControl surface, int params, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+            this.flags = params;
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
@@ -135,6 +233,36 @@
             this.visible = visible;
         }
 
+
+        /**
+         * Constructs surface parameters to be applied when the current view state gets pushed to
+         * RenderThread.
+         *
+         * @param surface The surface to modify.
+         * @param alpha Alpha to apply.
+         * @param matrix Matrix to apply.
+         * @param windowCrop Crop to apply.
+         * @param layer The layer to apply.
+         * @param cornerRadius The corner radius to apply.
+         * @param visible The visibility to apply.
+         *
+         * @deprecated Use {@link SurfaceParams.Builder} to create an instance.
+         */
+        @Deprecated
+        public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+                Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+            this.flags = FLAG_ALL;
+            this.surface = surface;
+            this.alpha = alpha;
+            this.matrix = new Matrix(matrix);
+            this.windowCrop = new Rect(windowCrop);
+            this.layer = layer;
+            this.cornerRadius = cornerRadius;
+            this.visible = visible;
+        }
+
+        private final int flags;
+
         @VisibleForTesting
         public final SurfaceControl surface;
 
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 484d9a1..dc8bf9b 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -71,7 +71,9 @@
 
     private boolean mIsAllWindowsCached;
 
-    private final SparseArray<AccessibilityWindowInfo> mWindowCache =
+    // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
+    // The key of outer SparseArray is display ID and the key of inner SparseArray is window ID.
+    private final SparseArray<SparseArray<AccessibilityWindowInfo>> mWindowCacheByDisplay =
             new SparseArray<>();
 
     private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
@@ -84,34 +86,66 @@
         mAccessibilityNodeRefresher = nodeRefresher;
     }
 
-    public void setWindows(List<AccessibilityWindowInfo> windows) {
+    /**
+     * Sets all {@link AccessibilityWindowInfo}s of all displays into the cache.
+     * The key of SparseArray is display ID.
+     *
+     * @param windowsOnAllDisplays The accessibility windows of all displays.
+     */
+    public void setWindowsOnAllDisplays(
+            SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays) {
         synchronized (mLock) {
             if (DEBUG) {
                 Log.i(LOG_TAG, "Set windows");
             }
-            clearWindowCache();
-            if (windows == null) {
+            clearWindowCacheLocked();
+            if (windowsOnAllDisplays == null) {
                 return;
             }
-            final int windowCount = windows.size();
-            for (int i = 0; i < windowCount; i++) {
-                final AccessibilityWindowInfo window = windows.get(i);
-                addWindow(window);
+
+            final int displayCounts = windowsOnAllDisplays.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final List<AccessibilityWindowInfo> windowsOfDisplay =
+                        windowsOnAllDisplays.valueAt(i);
+
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                final int displayId = windowsOnAllDisplays.keyAt(i);
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    addWindowByDisplayLocked(displayId, windowsOfDisplay.get(j));
+                }
             }
             mIsAllWindowsCached = true;
         }
     }
 
+    /**
+     * Sets an {@link AccessibilityWindowInfo} into the cache.
+     *
+     * @param window The accessibility window.
+     */
     public void addWindow(AccessibilityWindowInfo window) {
         synchronized (mLock) {
             if (DEBUG) {
-                Log.i(LOG_TAG, "Caching window: " + window.getId());
+                Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ "
+                        + window.getDisplayId() + " ]");
             }
-            final int windowId = window.getId();
-            mWindowCache.put(windowId, new AccessibilityWindowInfo(window));
+            addWindowByDisplayLocked(window.getDisplayId(), window);
         }
     }
 
+    private void addWindowByDisplayLocked(int displayId, AccessibilityWindowInfo window) {
+        SparseArray<AccessibilityWindowInfo> windows = mWindowCacheByDisplay.get(displayId);
+        if (windows == null) {
+            windows = new SparseArray<>();
+            mWindowCacheByDisplay.put(displayId, windows);
+        }
+        final int windowId = window.getId();
+        windows.put(windowId, new AccessibilityWindowInfo(window));
+    }
     /**
      * Notifies the cache that the something in the UI changed. As a result
      * the cache will either refresh some nodes or evict some nodes.
@@ -236,44 +270,82 @@
         }
     }
 
-    public List<AccessibilityWindowInfo> getWindows() {
+    /**
+     * Gets all {@link AccessibilityWindowInfo}s of all displays from the cache.
+     *
+     * @return All cached {@link AccessibilityWindowInfo}s of all displays
+     *         or null if such not found. The key of SparseArray is display ID.
+     */
+    public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
         synchronized (mLock) {
             if (!mIsAllWindowsCached) {
                 return null;
             }
+            final SparseArray<List<AccessibilityWindowInfo>> returnWindows = new SparseArray<>();
+            final int displayCounts = mWindowCacheByDisplay.size();
 
-            final int windowCount = mWindowCache.size();
-            if (windowCount > 0) {
-                // Careful to return the windows in a decreasing layer order.
-                SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
-                sortedWindows.clear();
+            if (displayCounts > 0) {
+                for (int i = 0; i < displayCounts; i++) {
+                    final int displayId = mWindowCacheByDisplay.keyAt(i);
+                    final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                            mWindowCacheByDisplay.valueAt(i);
 
-                for (int i = 0; i < windowCount; i++) {
-                    AccessibilityWindowInfo window = mWindowCache.valueAt(i);
-                    sortedWindows.put(window.getLayer(), window);
+                    if (windowsOfDisplay == null) {
+                        continue;
+                    }
+
+                    final int windowCount = windowsOfDisplay.size();
+                    if (windowCount > 0) {
+                        // Careful to return the windows in a decreasing layer order.
+                        SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+                        sortedWindows.clear();
+
+                        for (int j = 0; j < windowCount; j++) {
+                            AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+                            sortedWindows.put(window.getLayer(), window);
+                        }
+
+                        // It's possible in transient conditions for two windows to share the same
+                        // layer, which results in sortedWindows being smaller than
+                        // mWindowCacheByDisplay
+                        final int sortedWindowCount = sortedWindows.size();
+                        List<AccessibilityWindowInfo> windows =
+                                new ArrayList<>(sortedWindowCount);
+                        for (int j = sortedWindowCount - 1; j >= 0; j--) {
+                            AccessibilityWindowInfo window = sortedWindows.valueAt(j);
+                            windows.add(new AccessibilityWindowInfo(window));
+                            sortedWindows.removeAt(j);
+                        }
+                        returnWindows.put(displayId, windows);
+                    }
                 }
-
-                // It's possible in transient conditions for two windows to share the same
-                // layer, which results in sortedWindows being smaller than mWindowCache
-                final int sortedWindowCount = sortedWindows.size();
-                List<AccessibilityWindowInfo> windows = new ArrayList<>(sortedWindowCount);
-                for (int i = sortedWindowCount - 1; i >= 0; i--) {
-                    AccessibilityWindowInfo window = sortedWindows.valueAt(i);
-                    windows.add(new AccessibilityWindowInfo(window));
-                    sortedWindows.removeAt(i);
-                }
-
-                return windows;
+                return returnWindows;
             }
             return null;
         }
     }
 
+    /**
+     * Gets an {@link AccessibilityWindowInfo} by windowId.
+     *
+     * @param windowId The id of the window.
+     *
+     * @return The {@link AccessibilityWindowInfo} or null if such not found.
+     */
     public AccessibilityWindowInfo getWindow(int windowId) {
         synchronized (mLock) {
-            AccessibilityWindowInfo window = mWindowCache.get(windowId);
-            if (window != null) {
-                return new AccessibilityWindowInfo(window);
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+                if (window != null) {
+                    return new AccessibilityWindowInfo(window);
+                }
             }
             return null;
         }
@@ -358,7 +430,7 @@
             if (DEBUG) {
                 Log.i(LOG_TAG, "clear()");
             }
-            clearWindowCache();
+            clearWindowCacheLocked();
             final int nodesForWindowCount = mNodeCache.size();
             for (int i = nodesForWindowCount - 1; i >= 0; i--) {
                 final int windowId = mNodeCache.keyAt(i);
@@ -370,8 +442,23 @@
         }
     }
 
-    private void clearWindowCache() {
-        mWindowCache.clear();
+    private void clearWindowCacheLocked() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "clearWindowCacheLocked");
+        }
+        final int displayCounts = mWindowCacheByDisplay.size();
+
+        if (displayCounts > 0) {
+            for (int i = displayCounts - 1; i >= 0; i--) {
+                final int displayId = mWindowCacheByDisplay.keyAt(i);
+                final SparseArray<AccessibilityWindowInfo> windows =
+                        mWindowCacheByDisplay.get(displayId);
+                if (windows != null) {
+                    windows.clear();
+                }
+                mWindowCacheByDisplay.remove(displayId);
+            }
+        }
         mIsAllWindowsCached = false;
     }
 
@@ -444,32 +531,41 @@
     public void checkIntegrity() {
         synchronized (mLock) {
             // Get the root.
-            if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
+            if (mWindowCacheByDisplay.size() <= 0 && mNodeCache.size() == 0) {
                 return;
             }
 
             AccessibilityWindowInfo focusedWindow = null;
             AccessibilityWindowInfo activeWindow = null;
 
-            final int windowCount = mWindowCache.size();
-            for (int i = 0; i < windowCount; i++) {
-                AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
 
-                // Check for one active window.
-                if (window.isActive()) {
-                    if (activeWindow != null) {
-                        Log.e(LOG_TAG, "Duplicate active window:" + window);
-                    } else {
-                        activeWindow = window;
-                    }
+                if (windowsOfDisplay == null) {
+                    continue;
                 }
 
-                // Check for one focused window.
-                if (window.isFocused()) {
-                    if (focusedWindow != null) {
-                        Log.e(LOG_TAG, "Duplicate focused window:" + window);
-                    } else {
-                        focusedWindow = window;
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    final AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+
+                    // Check for one active window.
+                    if (window.isActive()) {
+                        if (activeWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate active window:" + window);
+                        } else {
+                            activeWindow = window;
+                        }
+                    }
+                    // Check for one focused window.
+                    if (window.isFocused()) {
+                        if (focusedWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate focused window:" + window);
+                        } else {
+                            focusedWindow = window;
+                        }
                     }
                 }
             }
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 4db6f4f..d9fa9f2 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.SparseArray;
+import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -267,12 +268,14 @@
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
-                List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
-                if (windows != null) {
+                SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                        sAccessibilityCache.getWindowsOnAllDisplays();
+                List<AccessibilityWindowInfo> windows;
+                if (allWindows != null) {
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Windows cache hit");
                     }
-                    return windows;
+                    return allWindows.valueAt(Display.DEFAULT_DISPLAY);
                 }
                 if (DEBUG) {
                     Log.i(LOG_TAG, "Windows cache miss");
@@ -284,7 +287,9 @@
                     Binder.restoreCallingIdentity(identityToken);
                 }
                 if (windows != null) {
-                    sAccessibilityCache.setWindows(windows);
+                    allWindows = new SparseArray<>();
+                    allWindows.put(Display.DEFAULT_DISPLAY, windows);
+                    sAccessibilityCache.setWindowsOnAllDisplays(allWindows);
                     return windows;
                 }
             } else {
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index b3b0b72..5e02de4 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -89,6 +89,11 @@
     }
 
     @Override
+    public void internalNotifySessionLifecycle(boolean started) {
+        getMainCaptureSession().notifySessionLifecycle(mId, started);
+    }
+
+    @Override
     boolean isContentCaptureEnabled() {
         return getMainCaptureSession().isContentCaptureEnabled();
     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 625fcda..cf08c18 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -439,6 +439,19 @@
     public abstract void internalNotifyViewTreeEvent(boolean started);
 
     /**
+     * Notifies the Content Capture Service that a session has paused/resumed.
+     *
+     * @param started whether session has resumed.
+     */
+    public final void notifySessionLifecycle(boolean started) {
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifySessionLifecycle(started);
+    }
+
+    abstract void internalNotifySessionLifecycle(boolean started);
+
+    /**
      * Creates a {@link ViewStructure} for a "standard" view.
      *
      * <p>This method should be called after a visible view is laid out; the view then must populate
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 1e7440b..349ef09 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -583,6 +583,11 @@
     }
 
     @Override
+    public void internalNotifySessionLifecycle(boolean started) {
+        notifySessionLifecycle(mId, started);
+    }
+
+    @Override
     boolean isContentCaptureEnabled() {
         return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
     }
@@ -637,10 +642,9 @@
         sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
     }
 
-    /** Public because is also used by ViewRootImpl */
-    public void notifySessionLifecycle(boolean started) {
+    void notifySessionLifecycle(int sessionId, boolean started) {
         final int type = started ? TYPE_SESSION_RESUMED : TYPE_SESSION_PAUSED;
-        sendEvent(new ContentCaptureEvent(mId, type), FORCE_FLUSH);
+        sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
     }
 
     void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cac75cfd..57ce28e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2561,7 +2561,8 @@
      * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
      */
     private boolean shouldBlink() {
-        if (!isCursorVisible() || !mTextView.isFocused()) return false;
+        if (!isCursorVisible() || !mTextView.isFocused() ||
+                !mTextView.isVisibleToUser()) return false;
 
         final int start = mTextView.getSelectionStart();
         if (start < 0) return false;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 1a22a70..6bce651 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.View;
 
 import androidx.test.filters.LargeTest;
@@ -51,12 +53,17 @@
 public class AccessibilityCacheTest {
     private static final int WINDOW_ID_1 = 0xBEEF;
     private static final int WINDOW_ID_2 = 0xFACE;
+    private static final int WINDOW_ID_3 = 0xABCD;
+    private static final int WINDOW_ID_4 = 0xDCBA;
     private static final int SINGLE_VIEW_ID = 0xCAFE;
     private static final int OTHER_VIEW_ID = 0xCAB2;
     private static final int PARENT_VIEW_ID = 0xFED4;
     private static final int CHILD_VIEW_ID = 0xFEED;
     private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
     private static final int MOCK_CONNECTION_ID = 1;
+    private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+    private static final int DEFAULT_WINDOW_LAYER = 0;
+    private static final int SPECIFIC_WINDOW_LAYER = 5;
 
     AccessibilityCache mAccessibilityCache;
     AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
@@ -70,7 +77,7 @@
 
     @After
     public void tearDown() {
-        // Make sure we're recycling all of our window and node infos
+        // Make sure we're recycling all of our window and node infos.
         mAccessibilityCache.clear();
         AccessibilityInteractionClient.getInstance().clearCache();
     }
@@ -78,7 +85,7 @@
     @Test
     public void testEmptyCache_returnsNull() {
         assertNull(mAccessibilityCache.getNode(0, 0));
-        assertNull(mAccessibilityCache.getWindows());
+        assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
         assertNull(mAccessibilityCache.getWindow(0));
     }
 
@@ -114,10 +121,11 @@
         try {
             windowInfo = AccessibilityWindowInfo.obtain();
             windowInfo.setId(WINDOW_ID_1);
+            windowInfo.setDisplayId(Display.DEFAULT_DISPLAY);
             mAccessibilityCache.addWindow(windowInfo);
             // Make a copy
             copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
-            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info
+            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info.
             windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
             assertEquals(copyOfInfo, windowFromCache);
         } finally {
@@ -129,39 +137,40 @@
 
     @Test
     public void addWindowThenClear_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.clear();
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
     }
 
     @Test
     public void addWindowGetOtherId_returnsNull() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
     }
 
     @Test
     public void addWindowThenGetWindows_returnsNull() {
-        putWindowWithIdInCache(WINDOW_ID_1);
-        assertNull(mAccessibilityCache.getWindows());
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
+        assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
     }
 
     @Test
     public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
-        AccessibilityWindowInfo windowInfo1 = null, windowInfo2 = null;
-        AccessibilityWindowInfo window1Out = null, window2Out = null;
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
         List<AccessibilityWindowInfo> windowsOut = null;
         try {
-            windowInfo1 = AccessibilityWindowInfo.obtain();
-            windowInfo1.setId(WINDOW_ID_1);
-            windowInfo1.setLayer(5);
-            windowInfo2 = AccessibilityWindowInfo.obtain();
-            windowInfo2.setId(WINDOW_ID_2);
-            windowInfo2.setLayer(windowInfo1.getLayer() + 1);
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
             List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
-            mAccessibilityCache.setWindows(windowsIn);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
 
-            windowsOut = mAccessibilityCache.getWindows();
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
 
@@ -182,8 +191,151 @@
     }
 
     @Test
+    public void setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        AccessibilityWindowInfo window3Out = null;
+        List<AccessibilityWindowInfo> windowsOut = null;
+        try {
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+
+            putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, Display.DEFAULT_DISPLAY,
+                    windowInfo1.getLayer() + 1);
+
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+            window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+
+            assertEquals(3, windowsOut.size());
+            assertEquals(windowInfo2, windowsOut.get(0));
+            assertEquals(windowInfo1, windowsOut.get(2));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+            assertEquals(window3Out, windowsOut.get(1));
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            window3Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void
+            setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        List<AccessibilityWindowInfo> windowsOut = null;
+        try {
+            // Sets windows to default display.
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+            // Adds one window to second display.
+            putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, SECONDARY_DISPLAY_ID,
+                    windowInfo1.getLayer() + 1);
+
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+            assertEquals(2, windowsOut.size());
+            assertEquals(windowInfo2, windowsOut.get(0));
+            assertEquals(windowInfo1, windowsOut.get(1));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        AccessibilityWindowInfo windowInfo3 = null;
+        AccessibilityWindowInfo windowInfo4 = null;
+        AccessibilityWindowInfo window3Out = null;
+        AccessibilityWindowInfo window4Out = null;
+        List<AccessibilityWindowInfo> windowsOut1 = null;
+        List<AccessibilityWindowInfo> windowsOut2 = null;
+        try {
+            // Prepares all windows for default display.
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
+            List<AccessibilityWindowInfo> windowsIn1 = Arrays.asList(windowInfo1, windowInfo2);
+            // Prepares all windows for second display.
+            windowInfo3 = obtainAccessibilityWindowInfo(WINDOW_ID_3, windowInfo1.getLayer() + 2);
+            windowInfo4 = obtainAccessibilityWindowInfo(WINDOW_ID_4, windowInfo1.getLayer() + 3);
+            List<AccessibilityWindowInfo> windowsIn2 = Arrays.asList(windowInfo3, windowInfo4);
+            // Sets all windows of all displays into A11y cache.
+            SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+            allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1);
+            allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2);
+            mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+            // Gets windows at default display.
+            windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+            assertEquals(2, windowsOut1.size());
+            assertEquals(windowInfo2, windowsOut1.get(0));
+            assertEquals(windowInfo1, windowsOut1.get(1));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+            // Gets windows at seocnd display.
+            windowsOut2 = getWindowsByDisplay(SECONDARY_DISPLAY_ID);
+            window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+            window4Out = mAccessibilityCache.getWindow(WINDOW_ID_4);
+
+            assertEquals(2, windowsOut2.size());
+            assertEquals(windowInfo4, windowsOut2.get(0));
+            assertEquals(windowInfo3, windowsOut2.get(1));
+            assertEquals(windowInfo3, window3Out);
+            assertEquals(windowInfo4, window4Out);
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            window3Out.recycle();
+            window4Out.recycle();
+            windowInfo3.recycle();
+            windowInfo4.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut1) {
+                windowInfo.recycle();
+            }
+            for (AccessibilityWindowInfo windowInfo : windowsOut2) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
     public void addWindowThenStateChangedEvent_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.onAccessibilityEvent(
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -191,7 +343,8 @@
 
     @Test
     public void addWindowThenWindowsChangedEvent_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.onAccessibilityEvent(
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -622,9 +775,16 @@
         }
     }
 
-    private void putWindowWithIdInCache(int id) {
+    private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
         AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
-        windowInfo.setId(id);
+        windowInfo.setId(windowId);
+        windowInfo.setLayer(layer);
+        return windowInfo;
+    }
+
+    private void putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer) {
+        AccessibilityWindowInfo windowInfo = obtainAccessibilityWindowInfo(windowId, layer);
+        windowInfo.setDisplayId(displayId);
         mAccessibilityCache.addWindow(windowInfo);
         windowInfo.recycle();
     }
@@ -713,4 +873,20 @@
             }
         }
     }
+
+    private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) {
+        SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+        allWindows.put(displayId, windows);
+        mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+    }
+
+    private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                mAccessibilityCache.getWindowsOnAllDisplays();
+
+        if (allWindows != null && allWindows.size() > 0) {
+            return allWindows.get(displayId);
+        }
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 81ce15a4..c5da549 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -162,6 +162,11 @@
         }
 
         @Override
+        public void internalNotifySessionLifecycle(boolean started) {
+            throw new UnsupportedOperationException("Should not have been called");
+        }
+
+        @Override
         public void updateContentCaptureContext(ContentCaptureContext context) {
             throw new UnsupportedOperationException("should not have been called");
         }
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 8c0108d..602fe3e 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1264,9 +1264,7 @@
         }
         return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .addExtras(sNotificationBundle)
-                .setSmallIcon(
-                        isTv(context) ? R.drawable.ic_bug_report_black_24dp
-                                : com.android.internal.R.drawable.stat_sys_adb)
+                .setSmallIcon(R.drawable.ic_bug_report_black_24dp)
                 .setLocalOnly(true)
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d674be4..37fefc2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -76,6 +76,16 @@
     plugins: ["dagger2-compiler-2.19"],
 }
 
+filegroup {
+    name: "SystemUI-tests-utils",
+    srcs: [
+        "tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java",
+        "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
+        "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
+    ],
+    path: "tests/src",
+}
+
 android_library {
     name: "SystemUI-tests",
     manifest: "tests/AndroidManifest.xml",
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
index 60435d0..d62c1d4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
@@ -22,7 +22,7 @@
 
 /**
  * Allows for additional sensors to be retrieved from
- * {@link com.android.systemui.util.AsyncSensorManager}.
+ * {@link com.android.systemui.util.sensors.AsyncSensorManager}.
  */
 @ProvidesInterface(action = SensorManagerPlugin.ACTION, version = SensorManagerPlugin.VERSION)
 public interface SensorManagerPlugin extends Plugin {
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 925e4fa..a1006a8 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -42,7 +42,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingHorizontal="24dp"
-        android:paddingBottom="48dp"
         android:paddingTop="8dp"
         android:gravity="@integer/biometric_dialog_text_gravity"
         android:textSize="16sp"
@@ -52,6 +51,7 @@
         android:id="@+id/biometric_icon"
         android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
         android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+        android:paddingTop="48dp"
         android:layout_gravity="center_horizontal"
         android:scaleType="fitXY" />
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
index 3ae2df5b..2797042 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
@@ -16,9 +16,10 @@
 
 package com.android.systemui.shared.system;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
-import android.os.RemoteException;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
@@ -32,62 +33,132 @@
  * previously set listener.
  */
 public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
-    private List<IPinnedStackListener> mListeners = new ArrayList<>();
+    private List<PinnedStackListener> mListeners = new ArrayList<>();
 
     /** Adds a listener to receive updates from the WindowManagerService. */
-    public void addListener(IPinnedStackListener listener) {
+    public void addListener(PinnedStackListener listener) {
         mListeners.add(listener);
     }
 
     /** Removes a listener so it will no longer receive updates from the WindowManagerService. */
-    public void removeListener(IPinnedStackListener listener) {
+    public void removeListener(PinnedStackListener listener) {
         mListeners.remove(listener);
     }
 
     @Override
-    public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onListenerRegistered(IPinnedStackController controller) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onListenerRegistered(controller);
         }
     }
 
     @Override
-    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
-            listener.onMovementBoundsChanged(
-                    insetBounds, normalBounds, animatingBounds,
-                    fromImeAdjustment, fromShelfAdjustment, displayRotation);
+    public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment,
+                    fromShelfAdjustment);
         }
     }
 
     @Override
-    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onImeVisibilityChanged(imeVisible, imeHeight);
         }
     }
 
     @Override
-    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
         }
     }
 
     @Override
-    public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onMinimizedStateChanged(boolean isMinimized) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onMinimizedStateChanged(isMinimized);
         }
     }
 
     @Override
-    public void onActionsChanged(ParceledListSlice actions) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onActionsChanged(ParceledListSlice actions) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onActionsChanged(actions);
         }
     }
+
+    @Override
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onSaveReentrySnapFraction(componentName, bounds);
+        }
+    }
+
+    @Override
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onResetReentrySnapFraction(componentName);
+        }
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onDisplayInfoChanged(displayInfo);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onConfigurationChanged();
+        }
+    }
+
+    @Override
+    public void onAspectRatioChanged(float aspectRatio) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onAspectRatioChanged(aspectRatio);
+        }
+    }
+
+    @Override
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
+        }
+    }
+
+    /**
+     * A counterpart of {@link IPinnedStackListener} with empty implementations.
+     * Subclasses can ignore those methods they do not intend to take action upon.
+     */
+    public static class PinnedStackListener {
+        public void onListenerRegistered(IPinnedStackController controller) {}
+
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {}
+
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+
+        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
+
+        public void onMinimizedStateChanged(boolean isMinimized) {}
+
+        public void onActionsChanged(ParceledListSlice actions) {}
+
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {}
+
+        public void onResetReentrySnapFraction(ComponentName componentName) {}
+
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {}
+
+        public void onConfigurationChanged() {}
+
+        public void onAspectRatioChanged(float aspectRatio) {}
+
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {}
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 794c30a..9f1a1fa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,12 +27,12 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.IPinnedStackListener;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 
 public class WindowManagerWrapper {
 
@@ -212,7 +212,7 @@
      * Adds a pinned stack listener, which will receive updates from the window manager service
      * along with any other pinned stack listeners that were added via this method.
      */
-    public void addPinnedStackListener(IPinnedStackListener listener) throws RemoteException {
+    public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException {
         mPinnedStackListenerForwarder.addListener(listener);
         WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
                 DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
@@ -221,7 +221,7 @@
     /**
      * Removes a pinned stack listener.
      */
-    public void removePinnedStackListener(IPinnedStackListener listener) {
+    public void removePinnedStackListener(PinnedStackListener listener) {
         mPinnedStackListenerForwarder.removeListener(listener);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bd5b9c7..ca6803d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,6 +15,7 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
 import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
@@ -110,10 +111,10 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -314,6 +315,7 @@
     @Inject Lazy<INotificationManager> mINotificationManager;
     @Inject Lazy<FalsingManager> mFalsingManager;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
+    @Inject Lazy<AlarmManager> mAlarmManager;
 
     @Inject
     public Dependency() {
@@ -508,6 +510,7 @@
         mProviders.put(INotificationManager.class, mINotificationManager::get);
         mProviders.put(FalsingManager.class, mFalsingManager::get);
         mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
+        mProviders.put(AlarmManager.class, mAlarmManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index d46a86c..239cbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
@@ -230,4 +231,11 @@
             @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
         return new DeviceProvisionedControllerImpl(context, mainHandler);
     }
+
+    /** */
+    @Singleton
+    @Provides
+    public AlarmManager provideAlarmManager(Context context) {
+        return context.getSystemService(AlarmManager.class);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
index ff4eb83..38f2f5e8 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
@@ -24,7 +24,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import javax.inject.Singleton;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e4d2005..73bbce9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -608,7 +608,10 @@
                         MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
             }
-            totalHeight += child.getMeasuredHeight();
+
+            if (child.getVisibility() != View.GONE) {
+                totalHeight += child.getMeasuredHeight();
+            }
         }
 
         // Use the new width so it's centered horizontally
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 67fc3e3..8211c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -48,7 +48,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -61,8 +60,6 @@
 import android.util.Pair;
 import android.util.SparseSetArray;
 import android.view.Display;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
@@ -76,6 +73,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -926,7 +924,9 @@
 
         @Override
         public void onSingleTaskDisplayDrawn(int displayId) {
-            final Bubble expandedBubble = getExpandedBubble(mContext);
+            final Bubble expandedBubble = mStackView != null
+                    ? mStackView.getExpandedBubble()
+                    : null;
             if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
                 expandedBubble.setContentVisibility(true);
             }
@@ -934,7 +934,9 @@
 
         @Override
         public void onSingleTaskDisplayEmpty(int displayId) {
-            final Bubble expandedBubble = getExpandedBubble(mContext);
+            final Bubble expandedBubble = mStackView != null
+                    ? mStackView.getExpandedBubble()
+                    : null;
             if (expandedBubble == null) {
                 return;
             }
@@ -993,32 +995,12 @@
     }
 
     /** PinnedStackListener that dispatches IME visibility updates to the stack. */
-    private class BubblesImeListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) throws RemoteException {}
-
+    private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mStackView != null && mStackView.getBubbleCount() > 0) {
                 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
             }
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-                throws RemoteException {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {}
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) throws RemoteException {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index c2b0fe4..20742d6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -43,7 +43,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.PrintWriter;
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 85bc22b..914258f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
 
 import android.content.Context;
+import android.hardware.SensorManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.DeviceConfig;
@@ -35,7 +36,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.io.PrintWriter;
 
@@ -66,6 +67,7 @@
             DeviceConfigProxy deviceConfig) {
         mProximitySensor = proximitySensor;
         mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
+        mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME);
         mDeviceConfig = deviceConfig;
         mDeviceConfigListener =
                 properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace());
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 3f5cae6..0aa66af 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -30,7 +30,7 @@
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
index bf39751..53ca783 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
@@ -19,7 +19,7 @@
 import android.view.MotionEvent;
 
 import com.android.systemui.classifier.Classifier;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.util.List;
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
index eeca409..f0feb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
@@ -23,7 +23,7 @@
 import android.view.MotionEvent;
 
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index c367286..bb8c7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,7 +33,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -45,7 +45,7 @@
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
     public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager) {
         Context context = dozeService;
-        SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
+        AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
         DockManager dockManager = Dependency.get(DockManager.class);
         WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
@@ -91,7 +91,7 @@
                 params.getPolicy());
     }
 
-    private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
+    private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
             DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
             DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
             DockManager dockManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 026a625..67eefc5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
@@ -43,11 +42,12 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.R;
+import com.android.systemui.Dependency;
 import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AlarmTimeout;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
@@ -62,7 +62,7 @@
 
     private final Context mContext;
     private final AlarmManager mAlarmManager;
-    private final SensorManager mSensorManager;
+    private final AsyncSensorManager mSensorManager;
     private final ContentResolver mResolver;
     private final TriggerSensor mPickupSensor;
     private final DozeParameters mDozeParameters;
@@ -74,13 +74,13 @@
     protected TriggerSensor[] mSensors;
 
     private final Handler mHandler = new Handler();
-    private final ProxSensor mProxSensor;
+    private final ProximitySensor mProximitySensor;
     private long mDebounceFrom;
     private boolean mSettingRegistered;
     private boolean mListening;
     private boolean mPaused;
 
-    public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
+    public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
             DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
             Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
         mContext = context;
@@ -91,6 +91,7 @@
         mWakeLock = wakeLock;
         mProxCallback = proxCallback;
         mResolver = mContext.getContentResolver();
+        mCallback = callback;
 
         boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
         mSensors = new TriggerSensor[] {
@@ -146,8 +147,11 @@
                         false /* touchscreen */, mConfig.getWakeLockScreenDebounce()),
         };
 
-        mProxSensor = new ProxSensor(policy);
-        mCallback = callback;
+        mProximitySensor = new ProximitySensor(
+                context, sensorManager, Dependency.get(PluginManager.class));
+
+        mProximitySensor.register(
+                proximityEvent -> mProxCallback.accept(!proximityEvent.getNear()));
     }
 
     /**
@@ -236,7 +240,15 @@
     }
 
     public void setProxListening(boolean listen) {
-        mProxSensor.setRequested(listen);
+        if (mProximitySensor.isRegistered() && listen) {
+            mProximitySensor.alertListeners();
+        } else {
+            if (listen) {
+                mProximitySensor.resume();
+            } else {
+                mProximitySensor.pause();
+            }
+        }
     }
 
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@@ -267,115 +279,16 @@
     /** Dump current state */
     public void dump(PrintWriter pw) {
         for (TriggerSensor s : mSensors) {
-            pw.print("  Sensor: "); pw.println(s.toString());
+            pw.println("  Sensor: " + s.toString());
         }
-        pw.print("  ProxSensor: "); pw.println(mProxSensor.toString());
+        pw.println("  ProxSensor: " + mProximitySensor.toString());
     }
 
     /**
-     * @return true if prox is currently far, false if near or null if unknown.
+     * @return true if prox is currently near, false if far or null if unknown.
      */
-    public Boolean isProximityCurrentlyFar() {
-        return mProxSensor.mCurrentlyFar;
-    }
-
-    private class ProxSensor implements SensorEventListener {
-
-        boolean mRequested;
-        boolean mRegistered;
-        Boolean mCurrentlyFar;
-        long mLastNear;
-        final AlarmTimeout mCooldownTimer;
-        final AlwaysOnDisplayPolicy mPolicy;
-        final Sensor mSensor;
-        final boolean mUsingBrightnessSensor;
-
-        public ProxSensor(AlwaysOnDisplayPolicy policy) {
-            mPolicy = policy;
-            mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
-                    "prox_cooldown", mHandler);
-
-            // The default prox sensor can be noisy, so let's use a prox gated brightness sensor
-            // if available.
-            Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
-                    mContext.getString(R.string.doze_brightness_sensor_type));
-            mUsingBrightnessSensor = sensor != null;
-            if (sensor == null) {
-                sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            }
-            mSensor = sensor;
-        }
-
-        void setRequested(boolean requested) {
-            if (mRequested == requested) {
-                // Send an update even if we don't re-register.
-                mHandler.post(() -> {
-                    if (mCurrentlyFar != null) {
-                        mProxCallback.accept(mCurrentlyFar);
-                    }
-                });
-                return;
-            }
-            mRequested = requested;
-            updateRegistered();
-        }
-
-        private void updateRegistered() {
-            setRegistered(mRequested && !mCooldownTimer.isScheduled());
-        }
-
-        private void setRegistered(boolean register) {
-            if (mRegistered == register) {
-                return;
-            }
-            if (register) {
-                mRegistered = mSensorManager.registerListener(this, mSensor,
-                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
-            } else {
-                mSensorManager.unregisterListener(this);
-                mRegistered = false;
-                mCurrentlyFar = null;
-            }
-        }
-
-        @Override
-        public void onSensorChanged(android.hardware.SensorEvent event) {
-            if (DEBUG) Log.d(TAG, "onSensorChanged " + event);
-
-            if (mUsingBrightnessSensor) {
-                // The custom brightness sensor is gated by the proximity sensor and will return 0
-                // whenever prox is covered.
-                mCurrentlyFar = event.values[0] > 0;
-            } else {
-                mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
-            }
-            mProxCallback.accept(mCurrentlyFar);
-
-            long now = SystemClock.elapsedRealtime();
-            if (mCurrentlyFar == null) {
-                // Sensor has been unregistered by the proxCallback. Do nothing.
-            } else if (!mCurrentlyFar) {
-                mLastNear = now;
-            } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
-                // If the last near was very recent, we might be using more power for prox
-                // wakeups than we're saving from turning of the screen. Instead, turn it off
-                // for a while.
-                mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
-                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
-                updateRegistered();
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-
-        @Override
-        public String toString() {
-            return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s,"
-                    + " sensor=%s}", mRegistered, mRequested, mCooldownTimer.isScheduled(),
-                    mCurrentlyFar, mSensor);
-        }
+    public Boolean isProximityCurrentlyNear() {
+        return mProximitySensor.isNear();
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index bab64db..80d4b63 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -24,10 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.metrics.LogMaker;
 import android.os.Handler;
@@ -39,16 +35,16 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
-import java.util.function.IntConsumer;
+import java.util.function.Consumer;
 
 /**
  * Handles triggers for ambient state changes.
@@ -67,13 +63,15 @@
      */
     private static boolean sWakeDisplaySensorState = true;
 
+    private static final int PROXIMITY_TIMEOUT_DELAY_MS = 500;
+
     private final Context mContext;
     private final DozeMachine mMachine;
     private final DozeSensors mDozeSensors;
     private final DozeHost mDozeHost;
     private final AmbientDisplayConfiguration mConfig;
     private final DozeParameters mDozeParameters;
-    private final SensorManager mSensorManager;
+    private final AsyncSensorManager mSensorManager;
     private final Handler mHandler;
     private final WakeLock mWakeLock;
     private final boolean mAllowPulseTriggers;
@@ -81,6 +79,7 @@
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
     private final DockEventListener mDockEventListener = new DockEventListener();
     private final DockManager mDockManager;
+    private final ProximitySensor.ProximityCheck mProxCheck;
 
     private long mNotificationPulseTime;
     private boolean mPulsePending;
@@ -89,7 +88,7 @@
 
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AlarmManager alarmManager, AmbientDisplayConfiguration config,
-            DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
+            DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
             WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
         mContext = context;
         mMachine = machine;
@@ -105,6 +104,9 @@
                 dozeParameters.getPolicy());
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
+        mProxCheck = new ProximitySensor.ProximityCheck(
+                new ProximitySensor(mContext, mSensorManager, null),
+                mHandler);
     }
 
     private void onNotification(Runnable onPulseSuppressedListener) {
@@ -134,25 +136,27 @@
         }
     }
 
-    private void proximityCheckThenCall(IntConsumer callback,
+    private void proximityCheckThenCall(Consumer<Boolean> callback,
             boolean alreadyPerformedProxCheck,
             int reason) {
-        Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar();
+        Boolean cachedProxNear = mDozeSensors.isProximityCurrentlyNear();
         if (alreadyPerformedProxCheck) {
-            callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
-        } else if (cachedProxFar != null) {
-            callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR);
+            callback.accept(null);
+        } else if (cachedProxNear != null) {
+            callback.accept(cachedProxNear);
         } else {
             final long start = SystemClock.uptimeMillis();
-            new ProximityCheck() {
-                @Override
-                public void onProximityResult(int result) {
-                    final long end = SystemClock.uptimeMillis();
-                    DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
-                            end - start, reason);
-                    callback.accept(result);
-                }
-            }.check();
+            mProxCheck.check(PROXIMITY_TIMEOUT_DELAY_MS, near -> {
+                final long end = SystemClock.uptimeMillis();
+                DozeLog.traceProximityResult(
+                        mContext,
+                        near == null ? false : near,
+                        end - start,
+                        reason);
+                callback.accept(near);
+                mWakeLock.release(TAG);
+            });
+            mWakeLock.acquire(TAG);
         }
     }
 
@@ -178,7 +182,7 @@
             }
         } else {
             proximityCheckThenCall((result) -> {
-                if (result == ProximityCheck.RESULT_NEAR) {
+                if (result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -267,7 +271,7 @@
 
         if (wake) {
             proximityCheckThenCall((result) -> {
-                if (result == ProximityCheck.RESULT_NEAR) {
+                if (result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -376,7 +380,7 @@
 
         mPulsePending = true;
         proximityCheckThenCall((result) -> {
-            if (result == ProximityCheck.RESULT_NEAR) {
+            if (result) {
                 // in pocket, abort pulse
                 DozeLog.tracePulseDropped(mContext, "inPocket");
                 mPulsePending = false;
@@ -412,104 +416,11 @@
         pw.print(" notificationPulseTime=");
         pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
 
-        pw.print(" pulsePending="); pw.println(mPulsePending);
+        pw.println(" pulsePending=" + mPulsePending);
         pw.println("DozeSensors:");
         mDozeSensors.dump(pw);
     }
 
-    /**
-     * @see DozeSensors.ProxSensor
-     */
-    private abstract class ProximityCheck implements SensorEventListener, Runnable {
-        private static final int TIMEOUT_DELAY_MS = 500;
-
-        protected static final int RESULT_UNKNOWN = 0;
-        protected static final int RESULT_NEAR = 1;
-        protected static final int RESULT_FAR = 2;
-        protected static final int RESULT_NOT_CHECKED = 3;
-
-        private boolean mRegistered;
-        private boolean mFinished;
-        private float mMaxRange;
-        private boolean mUsingBrightnessSensor;
-
-        protected abstract void onProximityResult(int result);
-
-        public void check() {
-            Preconditions.checkState(!mFinished && !mRegistered);
-            Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
-                    mContext.getString(R.string.doze_brightness_sensor_type));
-            mUsingBrightnessSensor = sensor != null;
-            if (sensor == null) {
-                sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            }
-            if (sensor == null) {
-                if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found");
-                finishWithResult(RESULT_UNKNOWN);
-                return;
-            }
-            mDozeSensors.setDisableSensorsInterferingWithProximity(true);
-
-            mMaxRange = sensor.getMaximumRange();
-            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
-                    mHandler);
-            mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
-            mWakeLock.acquire(TAG);
-            mRegistered = true;
-        }
-
-        /**
-         * @see DozeSensors.ProxSensor#onSensorChanged(SensorEvent)
-         */
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (event.values.length == 0) {
-                if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!");
-                finishWithResult(RESULT_UNKNOWN);
-            } else {
-                if (DozeMachine.DEBUG) {
-                    Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
-                }
-                final boolean isNear;
-                if (mUsingBrightnessSensor) {
-                    // The custom brightness sensor is gated by the proximity sensor and will
-                    // return 0 whenever prox is covered.
-                    isNear = event.values[0] == 0;
-                } else {
-                    isNear = event.values[0] < mMaxRange;
-                }
-                finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
-            }
-        }
-
-        @Override
-        public void run() {
-            if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout");
-            finishWithResult(RESULT_UNKNOWN);
-        }
-
-        private void finishWithResult(int result) {
-            if (mFinished) return;
-            boolean wasRegistered = mRegistered;
-            if (mRegistered) {
-                mHandler.removeCallbacks(this);
-                mSensorManager.unregisterListener(this);
-                mDozeSensors.setDisableSensorsInterferingWithProximity(false);
-                mRegistered = false;
-            }
-            onProximityResult(result);
-            if (wasRegistered) {
-                mWakeLock.release(TAG);
-            }
-            mFinished = true;
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // noop
-        }
-    }
-
     private class TriggerReceiver extends BroadcastReceiver {
         private boolean mRegistered;
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 30be775..6795bff 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -18,6 +18,7 @@
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -46,9 +47,6 @@
     private static final String TAG = PipBoundsHandler.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    // System.identityHashCode guarantees zero for null object.
-    private static final int INVALID_SYSTEM_IDENTITY_TOKEN = 0;
-
     private final Context mContext;
     private final IWindowManager mWindowManager;
     private final PipSnapAlgorithm mSnapAlgorithm;
@@ -58,7 +56,7 @@
     private final Point mTmpDisplaySize = new Point();
 
     private IPinnedStackController mPinnedStackController;
-    private int mLastPipToken;
+    private ComponentName mLastPipComponentName;
     private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
 
     private float mDefaultAspectRatio;
@@ -80,8 +78,11 @@
         mContext = context;
         mSnapAlgorithm = new PipSnapAlgorithm(context);
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
-        mAspectRatio = mDefaultAspectRatio;
         reloadResources();
+        // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
+        // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+        // triggers a configuration change and the resources to be reloaded.
+        mAspectRatio = mDefaultAspectRatio;
     }
 
     /**
@@ -161,27 +162,27 @@
     }
 
     /**
-     * Responds to IPinnedStackListener on saving reentry snap fraction for a given token.
-     * Token should be generated via {@link System#identityHashCode(Object)}
+     * Responds to IPinnedStackListener on saving reentry snap fraction
+     * for a given {@link ComponentName}.
      */
-    public void onSaveReentrySnapFraction(int token, Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipToken = token;
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        mReentrySnapFraction = getSnapFraction(bounds);
+        mLastPipComponentName = componentName;
     }
 
     /**
-     * Responds to IPinnedStackListener on resetting reentry snap fraction for a given token.
-     * Token should be generated via {@link System#identityHashCode(Object)}
+     * Responds to IPinnedStackListener on resetting reentry snap fraction
+     * for a given {@link ComponentName}.
      */
-    public void onResetReentrySnapFraction(int token) {
-        if (mLastPipToken == token) {
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        if (componentName.equals(mLastPipComponentName)) {
             onResetReentrySnapFractionUnchecked();
         }
     }
 
     private void onResetReentrySnapFractionUnchecked() {
         mReentrySnapFraction = INVALID_SNAP_FRACTION;
-        mLastPipToken = INVALID_SYSTEM_IDENTITY_TOKEN;
+        mLastPipComponentName = null;
     }
 
     /**
@@ -212,24 +213,28 @@
     /**
      * Responds to IPinnedStackListener on preparing the pinned stack animation.
      */
-    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
-        final Rect targetStackBounds;
-        if (stackBounds == null) {
-            targetStackBounds = getDefaultBounds(mReentrySnapFraction);
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        final Rect destinationBounds;
+        if (bounds == null) {
+            destinationBounds = getDefaultBounds(mReentrySnapFraction);
         } else {
-            targetStackBounds = new Rect();
-            targetStackBounds.set(stackBounds);
+            destinationBounds = new Rect(bounds);
         }
         if (isValidPictureInPictureAspectRatio(aspectRatio)) {
-            transformBoundsToAspectRatio(targetStackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
+            transformBoundsToAspectRatio(destinationBounds, aspectRatio,
+                    false /* useCurrentMinEdgeSize */);
         }
-        if (targetStackBounds.equals(stackBounds)) {
+        if (destinationBounds.equals(bounds)) {
             return;
         }
         mAspectRatio = aspectRatio;
         onResetReentrySnapFractionUnchecked();
-        // TODO: callback Window Manager on starting animation with calculated bounds
+        try {
+            mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
+                    -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to start PiP animation from SysUI", e);
+        }
     }
 
     /**
@@ -358,6 +363,7 @@
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
         pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
         pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
         pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
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 3be3422..8dfae32 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -32,14 +32,16 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -58,8 +60,12 @@
     private IActivityTaskManager mActivityTaskManager;
     private Handler mHandler = new Handler();
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
 
+    private PipBoundsHandler mPipBoundsHandler;
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
@@ -119,11 +125,11 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onListenerRegistered(IPinnedStackController controller) {
             mHandler.post(() -> {
+                mPipBoundsHandler.setPinnedStackController(controller);
                 mTouchHandler.setPinnedStackController(controller);
             });
         }
@@ -131,6 +137,7 @@
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
                 mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
@@ -138,31 +145,66 @@
         @Override
         public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
             mHandler.post(() -> {
-               mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mPipBoundsHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
             });
         }
 
         @Override
         public void onMinimizedStateChanged(boolean isMinimized) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onMinimizedStateChanged(isMinimized);
                 mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
             });
         }
 
         @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
-                        fromImeAdjustment, fromShelfAdjustment, displayRotation);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+                        mTmpDisplayInfo.rotation);
             });
         }
 
         @Override
         public void onActionsChanged(ParceledListSlice actions) {
+            mHandler.post(() -> mMenuController.setAppActions(actions));
+        }
+
+        @Override
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+            mHandler.post(() -> mPipBoundsHandler.onSaveReentrySnapFraction(componentName, bounds));
+        }
+
+        @Override
+        public void onResetReentrySnapFraction(ComponentName componentName) {
+            mHandler.post(() -> mPipBoundsHandler.onResetReentrySnapFraction(componentName));
+        }
+
+        @Override
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+            mHandler.post(() -> mPipBoundsHandler.onDisplayInfoChanged(displayInfo));
+        }
+
+        @Override
+        public void onConfigurationChanged() {
+            mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged());
+        }
+
+        @Override
+        public void onAspectRatioChanged(float aspectRatio) {
+            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+        }
+
+        @Override
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
             mHandler.post(() -> {
-                mMenuController.setAppActions(actions);
+                mPipBoundsHandler.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
             });
         }
     }
@@ -184,12 +226,13 @@
         }
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager);
         mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mActivityTaskManager,
-                mMenuController, mInputConsumerController);
+                mMenuController, mInputConsumerController, mPipBoundsHandler);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
 
@@ -252,5 +295,6 @@
         mInputConsumerController.dump(pw, innerPrefix);
         mMenuController.dump(pw, innerPrefix);
         mTouchHandler.dump(pw, innerPrefix);
+        mPipBoundsHandler.dump(pw, innerPrefix);
     }
 }
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 30cf412..1f36d97 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -48,6 +48,7 @@
 import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.systemui.R;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
@@ -75,6 +76,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final ViewConfiguration mViewConfig;
     private final PipMenuListener mMenuListener = new PipMenuListener();
+    private final PipBoundsHandler mPipBoundsHandler;
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
@@ -178,7 +180,8 @@
 
     public PipTouchHandler(Context context, IActivityManager activityManager,
             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
-            InputConsumerController inputConsumerController) {
+            InputConsumerController inputConsumerController,
+            PipBoundsHandler pipBoundsHandler) {
 
         // Initialize the Pip input consumer
         mContext = context;
@@ -211,6 +214,8 @@
         inputConsumerController.setInputListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
         onRegistrationChanged(inputConsumerController.isRegistered());
+
+        mPipBoundsHandler = pipBoundsHandler;
     }
 
     public void setTouchEnabled(boolean enabled) {
@@ -787,14 +792,8 @@
         mMovementBounds = isMenuExpanded
                 ? mExpandedMovementBounds
                 : mNormalMovementBounds;
-        try {
-            if (mPinnedStackController != null) {
-                mPinnedStackController.setMinEdgeSize(
-                        isMenuExpanded ? mExpandedShortestEdgeSize : 0);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set minimized state", e);
-        }
+        mPipBoundsHandler.setMinEdgeSize(
+                isMenuExpanded ? mExpandedShortestEdgeSize : 0);
     }
 
     /**
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 918af4f..81d6973 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -19,13 +19,10 @@
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -46,16 +43,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
+import android.view.DisplayInfo;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -111,9 +107,8 @@
     private int mSuspendPipResizingReason;
 
     private Context mContext;
-    private IActivityManager mActivityManager;
+    private PipBoundsHandler mPipBoundsHandler;
     private IActivityTaskManager mActivityTaskManager;
-    private IWindowManager mWindowManager;
     private MediaSessionManager mMediaSessionManager;
     private int mState = STATE_NO_PIP;
     private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
@@ -135,11 +130,16 @@
     private PipNotification mPipNotification;
     private ParceledListSlice mCustomActions;
 
+    // Used to calculate the movement bounds
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
+
     // Keeps track of the IME visibility to adjust the PiP when the IME is visible
     private boolean mImeVisible;
     private int mImeHeightAdjustment;
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
 
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
@@ -181,11 +181,7 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {}
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mState == STATE_PIP) {
@@ -205,17 +201,13 @@
         }
 
         @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {}
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mDefaultPipBounds.set(normalBounds);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mDefaultPipBounds.set(animatingBounds);
             });
         }
 
@@ -241,10 +233,8 @@
         }
         mInitialized = true;
         mContext = context;
-
-        mActivityManager = ActivityManager.getService();
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mActivityTaskManager = ActivityTaskManager.getService();
-        mWindowManager = WindowManagerGlobal.getWindowManagerService();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
@@ -291,7 +281,7 @@
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
 
         try {
-            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
+            WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 61d7498..1e763cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -222,7 +222,7 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
-        if (DEBUG) Log.d(TAG, "Recreating tiles");
+        Log.d(TAG, "Recreating tiles");
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
@@ -231,7 +231,7 @@
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
                 tile -> {
-                    if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
+                    Log.d(TAG, "Destroying tile: " + tile.getKey());
                     tile.getValue().destroy();
                 });
         final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
@@ -248,9 +248,10 @@
                     newTiles.put(tileSpec, tile);
                 } else {
                     tile.destroy();
+                    Log.d(TAG, "Destroying not available tile: " + tileSpec);
                 }
             } else {
-                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
+                Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
                     tile = createTile(tileSpec);
                     if (tile != null) {
@@ -259,6 +260,7 @@
                             newTiles.put(tileSpec, tile);
                         } else {
                             tile.destroy();
+                            Log.d(TAG, "Destroying not available tile: " + tileSpec);
                         }
                     }
                 } catch (Throwable t) {
@@ -274,7 +276,7 @@
         mTiles.putAll(newTiles);
         if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
             // If we didn't manage to create any tiles, set it to empty (default)
-            if (DEBUG) Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
+            Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
             changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
         } else {
             for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 276afa7..a70dc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -56,7 +57,8 @@
     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
     private val bypassController: KeyguardBypassController,
     private val headsUpManager: HeadsUpManagerPhone,
-    private val roundnessManager: NotificationRoundnessManager
+    private val roundnessManager: NotificationRoundnessManager,
+    private val statusBarStateController: StatusBarStateController
 ) : Gefingerpoken {
     companion object {
         private val RUBBERBAND_FACTOR_STATIC = 0.25f
@@ -188,7 +190,8 @@
             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
             MotionEvent.ACTION_UP -> {
                 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
-                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000
+                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
+                        statusBarStateController.state != StatusBarState.SHADE
                 if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
                     finishExpansion()
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 4422a81..8b9268e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -20,6 +20,7 @@
 import android.animation.ValueAnimator;
 import android.text.format.DateFormat;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -137,6 +138,11 @@
         // Record the to-be mState and mLastState
         recordHistoricalState(state, mState);
 
+        // b/139259891
+        if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) {
+            Log.e(TAG, "Invalid state transition: SHADE -> SHADE_LOCKED", new Throwable());
+        }
+
         synchronized (mListeners) {
             String tag = getClass().getSimpleName() + "#setState(" + state + ")";
             DejankUtils.startDetectingBlockingIpcs(tag);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 575b559..4cd3ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -18,7 +18,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -36,8 +35,6 @@
 import android.util.MathUtils;
 import android.util.StatsLog;
 import android.view.Gravity;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ISystemGestureExclusionListener;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -57,6 +54,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -72,35 +70,13 @@
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
             "gestures.back_timeout", 250);
 
-    private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {
-        }
-
+    private final PinnedStackListener mImeChangedListener = new PinnedStackListener() {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             // No need to thread jump, assignments are atomic
             mImeHeight = imeVisible ? imeHeight : 0;
             // TODO: Probably cancel any existing gesture
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
-        }
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
-        }
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) {
-        }
     };
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index f7bb97b..00b764b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.Dependency
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.Assert
-import com.android.systemui.util.AsyncSensorManager
+import com.android.systemui.util.sensors.AsyncSensorManager
 
 class KeyguardLiftController constructor(
     private val statusBarStateController: StatusBarStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java
deleted file mode 100644
index a905eba..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Simple wrapper around SensorManager customized for the Proximity sensor.
- */
-public class ProximitySensor {
-    private static final String TAG = "ProxSensor";
-    private static final boolean DEBUG = false;
-
-    private final Sensor mSensor;
-    private final AsyncSensorManager mSensorManager;
-    private final boolean mUsingBrightnessSensor;
-    private final float mMaxRange;
-
-    private SensorEventListener mSensorEventListener = new SensorEventListener() {
-        @Override
-        public synchronized void onSensorChanged(SensorEvent event) {
-            onSensorEvent(event);
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-    };
-    private boolean mNear;
-    private List<ProximitySensorListener> mListeners = new ArrayList<>();
-    private String mTag = null;
-
-    @Inject
-    public ProximitySensor(Context context, AsyncSensorManager sensorManager) {
-        mSensorManager = sensorManager;
-        Sensor sensor = findBrightnessSensor(context, sensorManager);
-
-        if (sensor == null) {
-            mUsingBrightnessSensor = false;
-            sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-        } else {
-            mUsingBrightnessSensor = true;
-        }
-        mSensor = sensor;
-        if (mSensor != null) {
-            mMaxRange = mSensor.getMaximumRange();
-        } else {
-            mMaxRange = 0;
-        }
-    }
-
-    public void setTag(String tag) {
-        mTag = tag;
-    }
-
-    private Sensor findBrightnessSensor(Context context, SensorManager sensorManager) {
-        String sensorType = context.getString(R.string.doze_brightness_sensor_type);
-        List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
-        Sensor sensor = null;
-        for (Sensor s : sensorList) {
-            if (sensorType.equals(s.getStringType())) {
-                sensor = s;
-                break;
-            }
-        }
-
-        return sensor;
-    }
-
-    /**
-     * Returns {@code false} if a Proximity sensor is not available.
-     */
-    public boolean getSensorAvailable() {
-        return mSensor != null;
-    }
-
-    /**
-     * Add a listener.
-     *
-     * Registers itself with the {@link SensorManager} if this is the first listener
-     * added.
-     */
-    public boolean register(ProximitySensorListener listener) {
-        if (!getSensorAvailable()) {
-            return false;
-        }
-
-        logDebug("using brightness sensor? " + mUsingBrightnessSensor);
-        mListeners.add(listener);
-        if (mListeners.size() == 1) {
-            logDebug("registering sensor listener");
-            mSensorManager.registerListener(
-                    mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_GAME);
-        }
-
-        return true;
-    }
-
-    /**
-     * Remove a listener.
-     *
-     * If all listeners are removed from an instance of this class,
-     * it will unregister itself with the SensorManager.
-     */
-    public void unregister(ProximitySensorListener listener) {
-        mListeners.remove(listener);
-        if (mListeners.size() == 0) {
-            logDebug("unregistering sensor listener");
-            mSensorManager.unregisterListener(mSensorEventListener);
-        }
-    }
-
-    public boolean isNear() {
-        return getSensorAvailable() && mNear;
-    }
-
-    private void onSensorEvent(SensorEvent event) {
-        boolean near = event.values[0] < mMaxRange;
-        if (mUsingBrightnessSensor) {
-            near = event.values[0] == 0;
-        }
-        mNear = near;
-        mListeners.forEach(proximitySensorListener ->
-                proximitySensorListener.onProximitySensorEvent(
-                        new ProximityEvent(mNear, event.timestamp)));
-    }
-
-    /** Implement to be notified of ProximityEvents. */
-    public interface ProximitySensorListener {
-        /** Called when the ProximitySensor changes. */
-        void onProximitySensorEvent(ProximityEvent proximityEvent);
-    }
-
-    /**
-     * Returned when the near/far state of a {@link ProximitySensor} changes.
-     */
-    public static class ProximityEvent {
-        private final boolean mNear;
-        private final long mTimestampNs;
-
-        public ProximityEvent(boolean near, long timestampNs) {
-            mNear = near;
-            mTimestampNs = timestampNs;
-        }
-
-        public boolean getNear() {
-            return mNear;
-        }
-
-        public long getTimestampNs() {
-            return mTimestampNs;
-        }
-    }
-
-    private void logDebug(String msg) {
-        if (DEBUG) {
-            Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
rename to packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index b9c5ee5..dcd0c58 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util;
+package com.android.systemui.util.sensors;
 
 import android.content.Context;
 import android.hardware.HardwareBuffer;
@@ -56,23 +56,31 @@
 
     private final SensorManager mInner;
     private final List<Sensor> mSensorCache;
-    private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
-    @VisibleForTesting final Handler mHandler;
+    private final Handler mHandler;
     private final List<SensorManagerPlugin> mPlugins;
 
     @Inject
     public AsyncSensorManager(Context context, PluginManager pluginManager) {
-        this(context.getSystemService(SensorManager.class), pluginManager);
+        this(context.getSystemService(SensorManager.class), pluginManager, null);
     }
 
     @VisibleForTesting
-    AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) {
+    public AsyncSensorManager(
+            SensorManager sensorManager, PluginManager pluginManager, Handler handler) {
         mInner = sensorManager;
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        if (handler == null) {
+            HandlerThread handlerThread = new HandlerThread("async_sensor");
+            handlerThread.start();
+            mHandler = new Handler(handlerThread.getLooper());
+        } else {
+            mHandler = handler;
+        }
         mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
         mPlugins = new ArrayList<>();
-        pluginManager.addPluginListener(this, SensorManagerPlugin.class, true /* allowMultiple */);
+        if (pluginManager != null) {
+            pluginManager.addPluginListener(this, SensorManagerPlugin.class,
+                    true /* allowMultiple */);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
new file mode 100644
index 0000000..c48bdde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Simple wrapper around SensorManager customized for the Proximity sensor.
+ */
+public class ProximitySensor {
+    private static final String TAG = "ProxSensor";
+    private static final boolean DEBUG = false;
+
+    private final Sensor mSensor;
+    private final AsyncSensorManager mSensorManager;
+    private final boolean mUsingBrightnessSensor;
+    private final float mMaxRange;
+    private List<ProximitySensorListener> mListeners = new ArrayList<>();
+    private String mTag = null;
+    private ProximityEvent mLastEvent;
+    private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
+    private boolean mPaused;
+    private boolean mRegistered;
+
+    private SensorEventListener mSensorEventListener = new SensorEventListener() {
+        @Override
+        public synchronized void onSensorChanged(SensorEvent event) {
+            onSensorEvent(event);
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    };
+
+    @Inject
+    public ProximitySensor(
+            Context context, AsyncSensorManager sensorManager, PluginManager pluginManager) {
+        mSensorManager = sensorManager;
+        Sensor sensor = findBrightnessSensor(context);
+
+        if (sensor == null) {
+            mUsingBrightnessSensor = false;
+            sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        } else {
+            mUsingBrightnessSensor = true;
+        }
+        mSensor = sensor;
+        if (mSensor != null) {
+            mMaxRange = mSensor.getMaximumRange();
+        } else {
+            mMaxRange = 0;
+        }
+    }
+
+    public void setTag(String tag) {
+        mTag = tag;
+    }
+
+    public void setSensorDelay(int sensorDelay) {
+        mSensorDelay = sensorDelay;
+    }
+
+    /**
+     * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+     */
+    public void pause() {
+        mPaused = true;
+        unregisterInternal();
+    }
+
+    /**
+     * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+     */
+    public void resume() {
+        mPaused = false;
+        registerInternal();
+    }
+
+    private Sensor findBrightnessSensor(Context context) {
+        String sensorType = context.getString(R.string.doze_brightness_sensor_type);
+        List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        Sensor sensor = null;
+        for (Sensor s : sensorList) {
+            if (sensorType.equals(s.getStringType())) {
+                sensor = s;
+                break;
+            }
+        }
+
+        return sensor;
+    }
+
+    /**
+     * Returns true if we are registered with the SensorManager.
+     */
+    public boolean isRegistered() {
+        return mRegistered;
+    }
+
+    /**
+     * Returns {@code false} if a Proximity sensor is not available.
+     */
+    public boolean getSensorAvailable() {
+        return mSensor != null;
+    }
+
+    /**
+     * Add a listener.
+     *
+     * Registers itself with the {@link SensorManager} if this is the first listener
+     * added. If a cool down is currently running, the sensor will be registered when it is over.
+     */
+    public boolean register(ProximitySensorListener listener) {
+        if (!getSensorAvailable()) {
+            return false;
+        }
+
+        logDebug("Using brightness sensor? " + mUsingBrightnessSensor);
+        mListeners.add(listener);
+        registerInternal();
+
+        return true;
+    }
+
+    private void registerInternal() {
+        if (mRegistered || mPaused || mListeners.isEmpty()) {
+            return;
+        }
+        logDebug("Registering sensor listener");
+        mRegistered = true;
+        mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
+    }
+
+    /**
+     * Remove a listener.
+     *
+     * If all listeners are removed from an instance of this class,
+     * it will unregister itself with the SensorManager.
+     */
+    public void unregister(ProximitySensorListener listener) {
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            unregisterInternal();
+        }
+    }
+
+    private void unregisterInternal() {
+        if (!mRegistered) {
+            return;
+        }
+        logDebug("unregistering sensor listener");
+        mSensorManager.unregisterListener(mSensorEventListener);
+        mRegistered = false;
+    }
+
+    public Boolean isNear() {
+        return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null;
+    }
+
+    /** Update all listeners with the last value this class received from the sensor. */
+    public void alertListeners() {
+        mListeners.forEach(proximitySensorListener ->
+                proximitySensorListener.onSensorEvent(mLastEvent));
+    }
+
+    private void onSensorEvent(SensorEvent event) {
+        boolean near = event.values[0] < mMaxRange;
+        if (mUsingBrightnessSensor) {
+            near = event.values[0] == 0;
+        }
+        mLastEvent = new ProximityEvent(near, event.timestamp);
+        alertListeners();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}",
+                isRegistered(), mPaused, isNear(), mSensor);
+    }
+
+    /**
+     * Convenience class allowing for briefly checking the proximity sensor.
+     */
+    public static class ProximityCheck implements Runnable {
+
+        private final ProximitySensor mSensor;
+        private final Handler mHandler;
+        private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
+
+        @Inject
+        public ProximityCheck(ProximitySensor sensor, Handler handler) {
+            mSensor = sensor;
+            mSensor.setTag("prox_check");
+            mHandler = handler;
+            mSensor.pause();
+            ProximitySensorListener listener = proximityEvent -> {
+                mCallbacks.forEach(
+                        booleanConsumer ->
+                                booleanConsumer.accept(
+                                        proximityEvent == null ? null : proximityEvent.getNear()));
+                mCallbacks.clear();
+                mSensor.pause();
+            };
+            mSensor.register(listener);
+        }
+
+        /** Set a descriptive tag for the sensors registration. */
+        public void setTag(String tag) {
+            mSensor.setTag(tag);
+        }
+
+        @Override
+        public void run() {
+            mSensor.pause();
+            mSensor.alertListeners();
+        }
+
+        /**
+         * Query the proximity sensor, timing out if no result.
+         */
+        public void check(long timeoutMs, Consumer<Boolean> callback) {
+            if (!mSensor.getSensorAvailable()) {
+                callback.accept(null);
+            }
+            mCallbacks.add(callback);
+            if (!mSensor.isRegistered()) {
+                mSensor.resume();
+                mHandler.postDelayed(this, timeoutMs);
+            }
+        }
+    }
+
+    /** Implement to be notified of ProximityEvents. */
+    public interface ProximitySensorListener {
+        /** Called when the ProximitySensor changes. */
+        void onSensorEvent(ProximityEvent proximityEvent);
+    }
+
+    /**
+     * Returned when the near/far state of a {@link ProximitySensor} changes.
+     */
+    public static class ProximityEvent {
+        private final boolean mNear;
+        private final long mTimestampNs;
+
+        public ProximityEvent(boolean near, long timestampNs) {
+            mNear = near;
+            mTimestampNs = timestampNs;
+        }
+
+        public boolean getNear() {
+            return mNear;
+        }
+
+        public long getTimestampNs() {
+            return mTimestampNs;
+        }
+
+        public long getTimestampMs() {
+            return mTimestampNs / 1000000;
+        }
+
+        @Override
+        public String toString() {
+            return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs);
+        }
+
+    }
+
+    private void logDebug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index b979356..3561e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -34,7 +34,7 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
index 3fc5d72..35d59c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
@@ -30,7 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.ProximitySensor;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 0c124ff..752e145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -24,7 +24,7 @@
 import static org.mockito.Mockito.withSettings;
 
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.utils.hardware.FakeSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
 
 import org.mockito.Answers;
 import org.mockito.MockSettings;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 392c677..aa62e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -40,7 +40,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.hardware.FakeSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index cd6d1e0..ddd1685 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -44,7 +44,7 @@
 import com.android.systemui.doze.DozeSensors.TriggerSensor;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index e190f99..f7cd696 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -32,6 +32,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
@@ -39,9 +40,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
 import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.util.wakelock.WakeLockFake;
-import com.android.systemui.utils.hardware.FakeSensorManager;
 
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -55,12 +57,8 @@
     private DozeTriggers mTriggers;
     private DozeMachine mMachine;
     private DozeHostFake mHost;
-    private AmbientDisplayConfiguration mConfig;
-    private DozeParameters mParameters;
     private FakeSensorManager mSensors;
     private Sensor mTapSensor;
-    private WakeLock mWakeLock;
-    private AlarmManager mAlarmManager;
     private DockManager mDockManagerFake;
 
     @BeforeClass
@@ -72,18 +70,21 @@
     @Before
     public void setUp() throws Exception {
         mMachine = mock(DozeMachine.class);
-        mAlarmManager = mock(AlarmManager.class);
+        AlarmManager alarmManager = mock(AlarmManager.class);
         mHost = spy(new DozeHostFake());
-        mConfig = DozeConfigurationUtil.createMockConfig();
-        mParameters = DozeConfigurationUtil.createMockParameters();
+        AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig();
+        DozeParameters parameters = DozeConfigurationUtil.createMockParameters();
         mSensors = spy(new FakeSensorManager(mContext));
         mTapSensor = mSensors.getFakeTapSensor().getSensor();
-        mWakeLock = new WakeLockFake();
+        WakeLock wakeLock = new WakeLockFake();
         mDockManagerFake = mock(DockManager.class);
+        AsyncSensorManager asyncSensorManager =
+                new AsyncSensorManager(mSensors, null, new Handler());
 
-        mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters,
-                mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true,
+        mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
+                asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
                 mDockManagerFake);
+        waitForSensorManager();
     }
 
     @Test
@@ -95,13 +96,14 @@
         clearInvocations(mMachine);
 
         mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
-        mSensors.getMockProximitySensor().sendProximityResult(false); /* Near */
+        mSensors.getFakeProximitySensor().sendProximityResult(false); /* Near */
 
         verify(mMachine, never()).requestState(any());
         verify(mMachine, never()).requestPulse(anyInt());
 
         mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
-        mSensors.getMockProximitySensor().sendProximityResult(true); /* Far */
+        waitForSensorManager();
+        mSensors.getFakeProximitySensor().sendProximityResult(true); /* Far */
 
         verify(mMachine).requestPulse(anyInt());
     }
@@ -111,6 +113,7 @@
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
 
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+        waitForSensorManager();
         verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
 
         clearInvocations(mSensors);
@@ -118,10 +121,12 @@
                 DozeMachine.State.DOZE_REQUEST_PULSE);
         mTriggers.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
                 DozeMachine.State.DOZE_PULSING);
+        waitForSensorManager();
         verify(mSensors).cancelTriggerSensor(any(), eq(mTapSensor));
 
         clearInvocations(mSensors);
         mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE);
+        waitForSensorManager();
         verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
     }
 
@@ -133,4 +138,8 @@
         mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
         verify(mDockManagerFake).removeListener(any());
     }
+
+    private void waitForSensorManager() {
+        TestableLooper.get(this).processAllMessages();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 2f24494..219aef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -131,7 +131,7 @@
                         mKeyguardBypassController);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
                 mKeyguardBypassController, mHeadsUpManager,
-                mock(NotificationRoundnessManager.class));
+                mock(NotificationRoundnessManager.class), mStatusBarStateController);
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
                 mKeyguardBypassController);
         mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
similarity index 71%
rename from packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 4a9b1b3..9149599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util;
+package com.android.systemui.util.sensors;
 
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -24,15 +23,15 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
+import android.os.Handler;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.utils.hardware.FakeSensorManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,20 +39,21 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class AsyncSensorManagerTest extends SysuiTestCase {
 
-    private TestableAsyncSensorManager mAsyncSensorManager;
-    private FakeSensorManager mFakeSensorManager;
+    private AsyncSensorManager mAsyncSensorManager;
     private SensorEventListener mListener;
-    private FakeSensorManager.MockProximitySensor mSensor;
+    private FakeSensorManager.FakeProximitySensor mSensor;
     private PluginManager mPluginManager;
 
     @Before
     public void setUp() throws Exception {
         mPluginManager = mock(PluginManager.class);
-        mFakeSensorManager = new FakeSensorManager(mContext);
-        mAsyncSensorManager = new TestableAsyncSensorManager(mFakeSensorManager);
-        mSensor = mFakeSensorManager.getMockProximitySensor();
+        FakeSensorManager fakeSensorManager = new FakeSensorManager(mContext);
+        mAsyncSensorManager = new AsyncSensorManager(
+                fakeSensorManager, mPluginManager, new Handler());
+        mSensor = fakeSensorManager.getFakeProximitySensor();
         mListener = mock(SensorEventListener.class);
     }
 
@@ -61,7 +61,7 @@
     public void registerListenerImpl() throws Exception {
         mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
 
-        mAsyncSensorManager.waitUntilRequestsCompleted();
+        waitUntilRequestsCompleted();
 
         // Verify listener was registered.
         mSensor.sendProximityResult(true);
@@ -73,7 +73,7 @@
         mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
         mAsyncSensorManager.unregisterListener(mListener);
 
-        mAsyncSensorManager.waitUntilRequestsCompleted();
+        waitUntilRequestsCompleted();
 
         // Verify listener was unregistered.
         mSensor.sendProximityResult(true);
@@ -85,7 +85,7 @@
         mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
         mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
 
-        mAsyncSensorManager.waitUntilRequestsCompleted();
+        waitUntilRequestsCompleted();
 
         // Verify listener was unregistered.
         mSensor.sendProximityResult(true);
@@ -98,13 +98,7 @@
                 eq(SensorManagerPlugin.class), eq(true) /* allowMultiple */);
     }
 
-    private class TestableAsyncSensorManager extends AsyncSensorManager {
-        public TestableAsyncSensorManager(SensorManager sensorManager) {
-            super(sensorManager, mPluginManager);
-        }
-
-        public void waitUntilRequestsCompleted() {
-            assertTrue(mHandler.runWithScissors(() -> {}, 0));
-        }
+    public void waitUntilRequestsCompleted() {
+        TestableLooper.get(this).processAllMessages();
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 29b8ab60..1deb495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.utils.hardware;
+package com.android.systemui.util.sensors;
 
 import android.content.Context;
 import android.hardware.HardwareBuffer;
@@ -54,7 +54,7 @@
 
     public static final String TAP_SENSOR_TYPE = "tapSensorType";
 
-    private final MockProximitySensor mMockProximitySensor;
+    private final FakeProximitySensor mFakeProximitySensor;
     private final FakeGenericSensor mFakeLightSensor;
     private final FakeGenericSensor mFakeTapSensor;
     private final FakeGenericSensor[] mSensors;
@@ -68,14 +68,14 @@
         }
 
         mSensors = new FakeGenericSensor[]{
-                mMockProximitySensor = new MockProximitySensor(proxSensor),
+                mFakeProximitySensor = new FakeProximitySensor(proxSensor),
                 mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
                 mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
         };
     }
 
-    public MockProximitySensor getMockProximitySensor() {
-        return mMockProximitySensor;
+    public FakeProximitySensor getFakeProximitySensor() {
+        return mFakeProximitySensor;
     }
 
     public FakeGenericSensor getFakeLightSensor() {
@@ -231,9 +231,9 @@
         setter.invoke(sensor, type);
     }
 
-    public class MockProximitySensor extends FakeGenericSensor {
+    public class FakeProximitySensor extends FakeGenericSensor {
 
-        private MockProximitySensor(Sensor sensor) {
+        private FakeProximitySensor(Sensor sensor) {
             super(sensor);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
new file mode 100644
index 0000000..6d13408
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ProximitySensorTest extends SysuiTestCase {
+
+    private ProximitySensor mProximitySensor;
+    private FakeSensorManager.FakeProximitySensor mFakeProximitySensor;
+
+    @Before
+    public void setUp() throws Exception {
+        FakeSensorManager sensorManager = new FakeSensorManager(getContext());
+        AsyncSensorManager asyncSensorManager = new AsyncSensorManager(
+                sensorManager, null, new Handler());
+        mFakeProximitySensor = sensorManager.getFakeProximitySensor();
+        mProximitySensor = new ProximitySensor(getContext(), asyncSensorManager, null);
+    }
+
+    @Test
+    public void testSingleListener() {
+        TestableListener listener = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+        mProximitySensor.register(listener);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listener.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+        mFakeProximitySensor.sendProximityResult(false);
+        assertTrue(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 2);
+
+        mProximitySensor.unregister(listener);
+        waitForSensorManager();
+    }
+
+    @Test
+    public void testMultiListener() {
+        TestableListener listenerA = new TestableListener();
+        TestableListener listenerB = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+
+        mProximitySensor.register(listenerA);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        mProximitySensor.register(listenerB);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listenerA.mLastEvent);
+        assertNull(listenerB.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listenerA.mLastEvent.getNear());
+        assertFalse(listenerB.mLastEvent.getNear());
+        assertEquals(listenerA.mCallCount, 1);
+        assertEquals(listenerB.mCallCount, 1);
+        mFakeProximitySensor.sendProximityResult(false);
+        assertTrue(listenerA.mLastEvent.getNear());
+        assertTrue(listenerB.mLastEvent.getNear());
+        assertEquals(listenerA.mCallCount, 2);
+        assertEquals(listenerB.mCallCount, 2);
+
+        mProximitySensor.unregister(listenerA);
+        mProximitySensor.unregister(listenerB);
+        waitForSensorManager();
+    }
+
+    @Test
+    public void testUnregister() {
+        TestableListener listener = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+        mProximitySensor.register(listener);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listener.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mProximitySensor.unregister(listener);
+        waitForSensorManager();
+        assertFalse(mProximitySensor.isRegistered());
+    }
+
+    @Test
+    public void testPauseAndResume() {
+        TestableListener listener = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+        mProximitySensor.register(listener);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listener.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mProximitySensor.pause();
+        waitForSensorManager();
+        assertFalse(mProximitySensor.isRegistered());
+
+        // More events do nothing when paused.
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+        mFakeProximitySensor.sendProximityResult(false);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mProximitySensor.resume();
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        // Still matches our previous call
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 2);
+
+        mProximitySensor.unregister(listener);
+        waitForSensorManager();
+        assertFalse(mProximitySensor.isRegistered());
+    }
+
+    @Test
+    public void testAlertListeners() {
+        TestableListener listenerA = new TestableListener();
+        TestableListener listenerB = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+
+        mProximitySensor.register(listenerA);
+        mProximitySensor.register(listenerB);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listenerA.mLastEvent);
+        assertNull(listenerB.mLastEvent);
+
+        mProximitySensor.alertListeners();
+        assertNull(listenerA.mLastEvent);
+        assertEquals(listenerA.mCallCount, 1);
+        assertNull(listenerB.mLastEvent);
+        assertEquals(listenerB.mCallCount, 1);
+
+        mFakeProximitySensor.sendProximityResult(false);
+        assertTrue(listenerA.mLastEvent.getNear());
+        assertEquals(listenerA.mCallCount, 2);
+        assertTrue(listenerB.mLastEvent.getNear());
+        assertEquals(listenerB.mCallCount, 2);
+
+        mProximitySensor.unregister(listenerA);
+        mProximitySensor.unregister(listenerB);
+        waitForSensorManager();
+    }
+
+    class TestableListener implements ProximitySensor.ProximitySensorListener {
+        ProximitySensor.ProximityEvent mLastEvent;
+        int mCallCount = 0;
+
+        @Override
+        public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) {
+            mLastEvent = proximityEvent;
+            mCallCount++;
+        }
+
+        void reset() {
+            mLastEvent = null;
+            mCallCount = 0;
+        }
+    };
+
+    private void waitForSensorManager() {
+        TestableLooper.get(this).processAllMessages();
+    }
+
+}
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 8ad2489..353a187 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1792,6 +1792,25 @@
 
   // Indicates if we are logging LinkSpeedCount in metrics
   optional bool link_speed_counts_logging_enabled = 4;
+
+  // Duration for evaluating Wifi condition to trigger a data stall
+  // measured in milliseconds
+  optional int32 data_stall_duration_ms = 5;
+
+  // Threshold of Tx throughput below which to trigger a data stall
+  // measured in Mbps
+  optional int32 data_stall_tx_tput_thr_mbps = 6;
+
+  // Threshold of Rx throughput below which to trigger a data stall
+  // measured in Mbps
+  optional int32 data_stall_rx_tput_thr_mbps = 7;
+
+  // Threshold of Tx packet error rate above which to trigger a data stall
+  // in percentage
+  optional int32 data_stall_tx_per_thr = 8;
+
+  // Threshold of CCA level above which to trigger a data stall in percentage
+  optional int32 data_stall_cca_level_thr = 9;
 }
 
 message WifiIsUnusableEvent {
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index e73abd1..fd64df9 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -63,8 +63,6 @@
     private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)");
     private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)");
 
-    private static final Pattern RSS_HIGH_WATERMARK_IN_KILOBYTES =
-            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
     private static final Pattern PROCFS_RSS_IN_KILOBYTES =
             Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
     private static final Pattern PROCFS_ANON_RSS_IN_KILOBYTES =
@@ -113,15 +111,6 @@
     }
 
     /**
-     * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
-     * /proc/PID/status in kilobytes or 0 if not available.
-     */
-    public static int readRssHighWaterMarkFromProcfs(int pid) {
-        final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
-        return parseVmHWMFromProcfs(readFileContents(statusPath));
-    }
-
-    /**
      * Reads cmdline of a process from procfs.
      *
      * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
@@ -204,19 +193,6 @@
     }
 
     /**
-     * Parses RSS high-water mark out from the contents of the /proc/pid/status file in procfs. The
-     * returned value is in kilobytes.
-     */
-    @VisibleForTesting
-    static int parseVmHWMFromProcfs(String procStatusContents) {
-        if (procStatusContents == null || procStatusContents.isEmpty()) {
-            return 0;
-        }
-        return (int) tryParseLong(RSS_HIGH_WATERMARK_IN_KILOBYTES, procStatusContents);
-    }
-
-
-    /**
      * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
      *
      * Parsing is required to strip anything after first null byte.
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 09f5286..96af74a 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -79,7 +79,7 @@
     private static final int MSG_SWITCH_USER = 1;
 
     private static final int RETRY_DELAY_TIME = 20; //ms
-    private static final int RETRY_TIMES = 30;
+    private static final int RETRY_TIMES = 60;
 
     // Maximum entries to keep in usage history before dumping out
     private static final int MAX_USAGE_HISTORY = 100;
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 3482e92..1cea4ca 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -301,7 +301,7 @@
                 // Greedily re-trigger the pre-reboot verification.
                 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
                         + "verified, resuming pre-reboot verification");
-                mPreRebootVerificationHandler.startPreRebootVerification(session);
+                mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
                 return;
             }
             if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
@@ -429,7 +429,7 @@
                             return;
                         }
                         mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
-                                originalSession);
+                                originalSession.sessionId);
                     });
             apkSession.commit(receiver.getIntentSender(), false);
             return;
@@ -526,7 +526,7 @@
 
     void commitSession(@NonNull PackageInstallerSession session) {
         updateStoredSession(session);
-        mPreRebootVerificationHandler.startPreRebootVerification(session);
+        mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
     }
 
     @Nullable
@@ -653,7 +653,7 @@
         if (!session.isStagedSessionReady()) {
             // The framework got restarted before the pre-reboot verification could complete,
             // restart the verification.
-            mPreRebootVerificationHandler.startPreRebootVerification(session);
+            mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
@@ -737,7 +737,16 @@
 
         @Override
         public void handleMessage(Message msg) {
-            PackageInstallerSession session = (PackageInstallerSession) msg.obj;
+            final int sessionId = msg.arg1;
+            final PackageInstallerSession session;
+            synchronized (mStagedSessions) {
+                session = mStagedSessions.get(sessionId);
+            }
+            // Maybe session was aborted before pre-reboot verification was complete
+            if (session == null) {
+                Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
+                return;
+            }
             switch (msg.what) {
                 case MSG_PRE_REBOOT_VERIFICATION_START:
                     handlePreRebootVerification_Start(session);
@@ -755,20 +764,20 @@
         }
 
         // Method for starting the pre-reboot verification
-        private void startPreRebootVerification(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, session).sendToTarget();
+        private void startPreRebootVerification(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
         }
 
-        private void notifyPreRebootVerification_Start_Complete(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session).sendToTarget();
+        private void notifyPreRebootVerification_Start_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
         }
 
-        private void notifyPreRebootVerification_Apex_Complete(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session).sendToTarget();
+        private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
         }
 
-        private void notifyPreRebootVerification_Apk_Complete(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session).sendToTarget();
+        private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
         }
 
         /**
@@ -778,7 +787,7 @@
          */
         private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
             Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
-            notifyPreRebootVerification_Start_Complete(session);
+            notifyPreRebootVerification_Start_Complete(session.sessionId);
         }
 
         /**
@@ -808,7 +817,7 @@
                 }
             }
 
-            notifyPreRebootVerification_Apex_Complete(session);
+            notifyPreRebootVerification_Apex_Complete(session.sessionId);
         }
 
         /**
@@ -819,7 +828,7 @@
          */
         private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
             if (!sessionContainsApk(session)) {
-                notifyPreRebootVerification_Apk_Complete(session);
+                notifyPreRebootVerification_Apk_Complete(session.sessionId);
                 return;
             }
 
diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
new file mode 100644
index 0000000..3076284
--- /dev/null
+++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import android.os.FileUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class ProcfsMemoryUtil {
+    private static final String TAG = "ProcfsMemoryUtil";
+
+    /** Path to procfs status file: /proc/pid/status. */
+    private static final String STATUS_FILE_FMT = "/proc/%d/status";
+
+    private static final Pattern RSS_HIGH_WATER_MARK_IN_KILOBYTES =
+            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
+
+    private ProcfsMemoryUtil() {}
+
+    /**
+     * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
+     * /proc/PID/status in kilobytes or 0 if not available.
+     */
+    static int readRssHighWaterMarkFromProcfs(int pid) {
+        final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+        return parseVmHWMFromStatus(readFile(statusPath));
+    }
+
+    /**
+     * Parses RSS high-water mark out from the contents of the /proc/pid/status file in procfs. The
+     * returned value is in kilobytes.
+     */
+    @VisibleForTesting
+    static int parseVmHWMFromStatus(String contents) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = RSS_HIGH_WATER_MARK_IN_KILOBYTES.matcher(contents);
+        try {
+            return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    private static String readFile(String path) {
+        try {
+            final File file = new File(path);
+            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+        } catch (IOException e) {
+            return "";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index e92abfd..19b8055 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -27,9 +27,9 @@
 import static com.android.server.am.MemoryStatUtil.readCmdlineFromProcfs;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs;
 import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readRssHighWaterMarkFromProcfs;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 50200a7c..2ab3e01 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -839,11 +839,12 @@
                 // so that the divider matches and remove this logic.
                 // TODO: This is currently only called when entering split-screen while in another
                 // task, and from the tests
-                // TODO (b/78247419): Check if launcher and overview are same then move home stack
-                // instead of recents stack. Then fix the rotation animation from fullscreen to
-                // minimized mode
+                // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode
+                final boolean isRecentsComponentHome =
+                        mService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser);
                 final ActivityStack recentStack = display.getOrCreateStack(
-                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS,
+                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS,
                         true /* onTop */);
                 recentStack.moveToFront("setWindowingMode");
                 // If task moved to docked stack - show recents if needed.
@@ -4940,12 +4941,6 @@
         }
     }
 
-
-    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-        if (getTaskStack() == null) return null;
-        return getTaskStack().getPictureInPictureBounds(aspectRatio, null /* currentStackBounds */);
-    }
-
     void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c933ece..47be792 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2342,7 +2342,12 @@
                             REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                             "reparentingHome");
                     mMovedToFront = true;
+                } else if (launchStack.topTask() == null) {
+                    // The task does not need to be reparented to the launch stack. Remove the
+                    // launch stack if there is no activity in it.
+                    launchStack.remove();
                 }
+
                 mOptions = null;
 
                 // We are moving a task to the front, use starting window to hide initial drawn
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 93b8f00..f647fe4 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -955,6 +955,9 @@
             updateReportedVisibilityLocked();
         }
 
+        // Reset the last saved PiP snap fraction on removal.
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
+
         mRemovingFromDisplay = false;
     }
 
@@ -1021,7 +1024,7 @@
         if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
         mAppStopped = true;
         // Reset the last saved PiP snap fraction on app stop.
-        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
         destroySurfaces();
         // Remove any starting window that was added for this app if they are still around.
         removeStartingWindow();
@@ -1705,10 +1708,7 @@
             return;
         }
 
-        if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
-            // Entering PiP from fullscreen, reset the snap fraction
-            mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
-        } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
+        if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
                 && !isHidden()) {
             // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
             // for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
@@ -1726,8 +1726,8 @@
                     stackBounds = mTmpRect;
                     pinnedStack.getBounds(stackBounds);
                 }
-                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
-                        stackBounds);
+                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(
+                        mActivityComponent, stackBounds);
             }
         } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
             initializeChangeTransition(mTmpPrevBounds);
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index ef0049b..8e57fec 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -50,7 +51,6 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -74,7 +74,7 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
 
-    public static final float INVALID_SNAP_FRACTION = -1f;
+    private static final float INVALID_SNAP_FRACTION = -1f;
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final Handler mHandler = UiThread.getHandler();
@@ -106,9 +106,6 @@
     private int mDefaultStackGravity;
     private float mDefaultAspectRatio;
     private Point mScreenEdgeInsets;
-    private int mCurrentMinSize;
-    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
-    private WeakReference<AppWindowToken> mLastPipActivity = null;
 
     // The aspect ratio bounds of the PIP.
     private float mMinAspectRatio;
@@ -118,7 +115,6 @@
     private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
     private final Rect mTmpInsets = new Rect();
     private final Rect mTmpRect = new Rect();
-    private final Rect mTmpAnimatingBoundsRect = new Rect();
     private final Point mTmpDisplaySize = new Point();
 
 
@@ -136,18 +132,21 @@
         }
 
         @Override
-        public void setMinEdgeSize(int minEdgeSize) {
-            mHandler.post(() -> {
-                mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
-            });
-        }
-
-        @Override
         public int getDisplayRotation() {
             synchronized (mService.mGlobalLock) {
                 return mDisplayInfo.rotation;
             }
         }
+
+        @Override
+        public void startAnimation(Rect destinationBounds, Rect sourceRectHint,
+                int animationDuration) {
+            synchronized (mService.mGlobalLock) {
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
+                pinnedStack.animateResizePinnedStack(destinationBounds,
+                        sourceRectHint, animationDuration, true /* fromFullscreen */);
+            }
+        }
     }
 
     /**
@@ -188,7 +187,6 @@
         final Resources res = mService.mContext.getResources();
         mDefaultMinSize = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
-        mCurrentMinSize = mDefaultMinSize;
         mDefaultAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
         final String screenEdgeInsetsDpString = res.getString(
@@ -216,6 +214,7 @@
             listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
             listener.onListenerRegistered(mCallbacks);
             mPinnedStackListener = listener;
+            notifyDisplayInfoChanged(mDisplayInfo);
             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
             notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
             // The movement bounds notification needs to be sent before the minimized state, since
@@ -238,58 +237,34 @@
     }
 
     /**
-     * Returns the current bounds (or the default bounds if there are no current bounds) with the
-     * specified aspect ratio.
-     */
-    Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
-            boolean useCurrentMinEdgeSize) {
-        // Save the snap fraction, calculate the aspect ratio based on screen size
-        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
-                getMovementBounds(stackBounds));
-
-        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
-        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
-                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
-        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
-        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
-        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
-        if (mIsMinimized) {
-            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
-        }
-        return stackBounds;
-    }
-
-    /**
      * Saves the current snap fraction for re-entry of the current activity into PiP.
      */
-    void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipActivity = new WeakReference<>(token);
+    void saveReentrySnapFraction(final ComponentName componentName, final Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onSaveReentrySnapFraction(componentName, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering save reentry fraction event.", e);
+        }
     }
 
     /**
      * Resets the last saved snap fraction so that the default bounds will be returned.
      */
-    void resetReentrySnapFraction(AppWindowToken token) {
-        if (mLastPipActivity != null && mLastPipActivity.get() == token) {
-            mReentrySnapFraction = INVALID_SNAP_FRACTION;
-            mLastPipActivity = null;
+    void resetReentrySnapFraction(ComponentName componentName) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onResetReentrySnapFraction(componentName);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
         }
     }
 
     /**
-     * @return the default bounds to show the PIP when there is no active PIP.
-     */
-    Rect getDefaultOrLastSavedBounds() {
-        return getDefaultBounds(mReentrySnapFraction);
-    }
-
-    /**
      * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
      * will apply the default bounds to the provided snap fraction.
      */
-    Rect getDefaultBounds(float snapFraction) {
+    private Rect getDefaultBounds(float snapFraction) {
         synchronized (mService.mGlobalLock) {
             final Rect insetBounds = new Rect();
             getInsetBounds(insetBounds);
@@ -311,13 +286,18 @@
         }
     }
 
+    private void setDisplayInfo(DisplayInfo displayInfo) {
+        mDisplayInfo.copyFrom(displayInfo);
+        notifyDisplayInfoChanged(mDisplayInfo);
+    }
+
     /**
      * In the case where the display rotation is changed but there is no stack, we can't depend on
      * onTaskStackBoundsChanged() to be called.  But we still should update our known display info
      * with the new state so that we can update SystemUI.
      */
     synchronized void onDisplayInfoChanged(DisplayInfo displayInfo) {
-        mDisplayInfo.copyFrom(displayInfo);
+        setDisplayInfo(displayInfo);
         notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
     }
 
@@ -335,7 +315,7 @@
             } else if (targetBounds.isEmpty()) {
                 // The stack is null, we are just initializing the stack, so just store the display
                 // info and ignore
-                mDisplayInfo.copyFrom(displayInfo);
+                setDisplayInfo(displayInfo);
                 outBounds.setEmpty();
                 return false;
             }
@@ -345,7 +325,8 @@
 
             // Calculate the snap fraction of the current stack along the old movement bounds
             final float snapFraction = getSnapFraction(postChangeStackBounds);
-            mDisplayInfo.copyFrom(displayInfo);
+
+            setDisplayInfo(displayInfo);
 
             // Calculate the stack bounds in the new orientation to the same same fraction along the
             // rotated movement bounds.
@@ -406,8 +387,11 @@
     void setAspectRatio(float aspectRatio) {
         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
             mAspectRatio = aspectRatio;
+            notifyAspectRatioChanged(aspectRatio);
             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
                     false /* fromShelfAdjustment */);
+            notifyPrepareAnimation(null /* sourceHintRect */, aspectRatio,
+                    null /* stackBounds */);
         }
     }
 
@@ -429,6 +413,10 @@
         notifyActionsChanged(mActions);
     }
 
+    void prepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        notifyPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+    }
+
     private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
             @NonNull DisplayInfo display2) {
         Preconditions.checkNotNull(display1);
@@ -461,6 +449,15 @@
         }
     }
 
+    private void notifyAspectRatioChanged(float aspectRatio) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onAspectRatioChanged(aspectRatio);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
+        }
+    }
+
     /**
      * Notifies listeners that the PIP minimized state has changed.
      */
@@ -497,23 +494,13 @@
                 return;
             }
             try {
-                final Rect insetBounds = new Rect();
-                getInsetBounds(insetBounds);
-                final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
-                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                            false /* useCurrentMinEdgeSize */);
-                }
-                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final Rect animatingBounds = new Rect();
                 final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                } else {
-                    animatingBounds.set(normalBounds);
                 }
-                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
-                        mDisplayInfo.rotation);
+                mPinnedStackListener.onMovementBoundsChanged(animatingBounds,
+                        fromImeAdjustment, fromShelfAdjustment);
             } catch (RemoteException e) {
                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
@@ -521,6 +508,30 @@
     }
 
     /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyDisplayInfoChanged(DisplayInfo displayInfo) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onDisplayInfoChanged(displayInfo);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering DisplayInfo changed event.", e);
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering prepare animation event.", e);
+        }
+    }
+
+    /**
      * @return the bounds on the screen that the PIP can be visible in.
      */
     private void getInsetBounds(Rect outRect) {
@@ -604,7 +615,6 @@
         pw.println(prefix + "  mImeHeight=" + mImeHeight);
         pw.println(prefix + "  mIsShelfShowing=" + mIsShelfShowing);
         pw.println(prefix + "  mShelfHeight=" + mShelfHeight);
-        pw.println(prefix + "  mReentrySnapFraction=" + mReentrySnapFraction);
         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 4b2d4ce..734f224 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -193,6 +193,9 @@
     /** Set when a power hint has started, but not ended. */
     private boolean mPowerHintSent;
 
+    /** Used to keep ensureActivitiesVisible() from being entered recursively. */
+    private boolean mInEnsureActivitiesVisible = false;
+
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
     int mDefaultMinSizeOfResizeableTaskDp = -1;
@@ -805,8 +808,14 @@
      */
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
-        mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
+        if (mInEnsureActivitiesVisible) {
+            // Don't do recursive work.
+            return;
+        }
+        mInEnsureActivitiesVisible = true;
+
         try {
+            mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
             // First the front stacks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -815,6 +824,7 @@
             }
         } finally {
             mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
+            mInEnsureActivitiesVisible = false;
         }
     }
 
@@ -959,10 +969,6 @@
         // Need to make sure the pinned stack exist so we can resize it below...
         stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
-        // Calculate the target bounds here before the task is reparented back into pinned windowing
-        // mode (which will reset the saved bounds)
-        final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
-
         try {
             final TaskRecord task = r.getTaskRecord();
             // Resize the pinned stack to match the current size of the task the activity we are
@@ -1001,9 +1007,14 @@
             mService.continueWindowLayout();
         }
 
-        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
-                true /* fromFullscreen */);
+        // Notify the pinned stack controller to prepare the PiP animation, expect callback
+        // delivered from SystemUI to WM to start the animation.
+        final PinnedStackController pinnedStackController =
+                display.mDisplayContent.getPinnedStackController();
+        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                null /* stackBounds */);
 
+        // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
         // stack.  This MUST run after the animation above is scheduled to ensure that the windows
         // drawn signal is scheduled after the bounds animation start call on the bounds animator
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 6a7f0c3..9465129 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1660,40 +1660,6 @@
     }
 
     /**
-     * @return the current stack bounds transformed to the given {@param aspectRatio}. If
-     *         the default bounds is {@code null}, then the {@param aspectRatio} is applied to the
-     *         default bounds.
-     */
-    Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
-        if (!mWmService.mAtmService.mSupportsPictureInPicture) {
-            return null;
-        }
-
-        final DisplayContent displayContent = getDisplayContent();
-        if (displayContent == null) {
-            return null;
-        }
-
-        if (!inPinnedWindowingMode()) {
-            return null;
-        }
-
-        final PinnedStackController pinnedStackController =
-                displayContent.getPinnedStackController();
-        if (stackBounds == null) {
-            // Calculate the aspect ratio bounds from the default bounds
-            stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
-        }
-
-        if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
-            return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
-        } else {
-            return stackBounds;
-        }
-    }
-
-    /**
      * Animates the pinned stack.
      */
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
@@ -1770,6 +1736,11 @@
             return;
         }
 
+        final DisplayContent displayContent = getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+
         if (!inPinnedWindowingMode()) {
             return;
         }
@@ -1780,13 +1751,10 @@
         if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) {
             return;
         }
-        getAnimationOrCurrentBounds(mTmpFromBounds);
-        mTmpToBounds.set(mTmpFromBounds);
-        getPictureInPictureBounds(aspectRatio, mTmpToBounds);
-        if (!mTmpToBounds.equals(mTmpFromBounds)) {
-            animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
-                    -1 /* duration */, false /* fromFullscreen */);
-        }
+
+        // Notify the pinned stack controller about aspect ratio change.
+        // This would result a callback delivered from SystemUI to WM to start animation,
+        // if the bounds are ought to be altered due to aspect ratio change.
         pinnedStackController.setAspectRatio(
                 pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
                         ? aspectRatio : -1f);
diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
index 6a0d7f1..9e3b54d 100644
--- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
@@ -22,7 +22,6 @@
 import static com.android.server.am.MemoryStatUtil.parseCmdlineFromProcfs;
 import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg;
 import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -230,18 +229,6 @@
     }
 
     @Test
-    public void testParseVmHWMFromProcfs_parsesCorrectValue() {
-        assertEquals(137668, parseVmHWMFromProcfs(PROC_STATUS_CONTENTS));
-    }
-
-    @Test
-    public void testParseVmHWMFromProcfs_emptyContents() {
-        assertEquals(0, parseVmHWMFromProcfs(""));
-
-        assertEquals(0, parseVmHWMFromProcfs(null));
-    }
-
-    @Test
     public void testParseCmdlineFromProcfs_invalidValue() {
         byte[] nothing = new byte[] {0x00, 0x74, 0x65, 0x73, 0x74}; // \0test
 
diff --git a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
new file mode 100644
index 0000000..4fb533f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import static com.android.server.stats.ProcfsMemoryUtil.parseVmHWMFromStatus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ProcfsMemoryUtilTest
+ */
+@SmallTest
+public class ProcfsMemoryUtilTest {
+    private static final String STATUS_CONTENTS = "Name:\tandroid.youtube\n"
+            + "State:\tS (sleeping)\n"
+            + "Tgid:\t12088\n"
+            + "Pid:\t12088\n"
+            + "PPid:\t723\n"
+            + "TracerPid:\t0\n"
+            + "Uid:\t10083\t10083\t10083\t10083\n"
+            + "Gid:\t10083\t10083\t10083\t10083\n"
+            + "Ngid:\t0\n"
+            + "FDSize:\t128\n"
+            + "Groups:\t3003 9997 20083 50083 \n"
+            + "VmPeak:\t 4546844 kB\n"
+            + "VmSize:\t 4542636 kB\n"
+            + "VmLck:\t       0 kB\n"
+            + "VmPin:\t       0 kB\n"
+            + "VmHWM:\t  137668 kB\n" // RSS high-water mark
+            + "VmRSS:\t  126776 kB\n" // RSS
+            + "RssAnon:\t   37860 kB\n"
+            + "RssFile:\t   88764 kB\n"
+            + "RssShmem:\t     152 kB\n"
+            + "VmData:\t 4125112 kB\n"
+            + "VmStk:\t    8192 kB\n"
+            + "VmExe:\t      24 kB\n"
+            + "VmLib:\t  102432 kB\n"
+            + "VmPTE:\t    1300 kB\n"
+            + "VmPMD:\t      36 kB\n"
+            + "VmSwap:\t      22 kB\n" // Swap
+            + "Threads:\t95\n"
+            + "SigQ:\t0/13641\n"
+            + "SigPnd:\t0000000000000000\n"
+            + "ShdPnd:\t0000000000000000\n"
+            + "SigBlk:\t0000000000001204\n"
+            + "SigIgn:\t0000000000000001\n"
+            + "SigCgt:\t00000006400084f8\n"
+            + "CapInh:\t0000000000000000\n"
+            + "CapPrm:\t0000000000000000\n"
+            + "CapEff:\t0000000000000000\n"
+            + "CapBnd:\t0000000000000000\n"
+            + "CapAmb:\t0000000000000000\n"
+            + "Seccomp:\t2\n"
+            + "Cpus_allowed:\tff\n"
+            + "Cpus_allowed_list:\t0-7\n"
+            + "Mems_allowed:\t1\n"
+            + "Mems_allowed_list:\t0\n"
+            + "voluntary_ctxt_switches:\t903\n"
+            + "nonvoluntary_ctxt_switches:\t104\n";
+
+    @Test
+    public void testParseVmHWMFromStatus_parsesCorrectValue() {
+        assertThat(parseVmHWMFromStatus(STATUS_CONTENTS)).isEqualTo(137668);
+    }
+
+    @Test
+    public void testParseVmHWMFromStatus_invalidValue() {
+        assertThat(parseVmHWMFromStatus("test\nVmHWM: x0x0x\ntest")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseVmHWMFromStatus_emptyContents() {
+        assertThat(parseVmHWMFromStatus("")).isEqualTo(0);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 4f00383..d311dfc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -38,7 +38,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.testing.DexmakerShareClassLoaderRule;
@@ -446,12 +445,7 @@
             final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor;
             if (mWindowingMode == WINDOWING_MODE_PINNED) {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
-                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
-                    @Override
-                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-                        return new Rect(50, 50, 100, 100);
-                    }
-                };
+                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop);
             } else {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
                         mWindowingMode, mActivityType, mOnTop);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
index efd468f..e9c2263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -66,8 +66,8 @@
 
         verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(false), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(false));
         verify(mIPinnedStackListener).onActionsChanged(any());
         verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
 
@@ -75,8 +75,8 @@
 
         mWm.setShelfHeight(true, SHELF_HEIGHT);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(true), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(true));
         verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
     }
 }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3548810..0abd9fc 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.telecom.Logging.Session;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IConnectionService;
 import com.android.internal.telecom.IConnectionServiceAdapter;
@@ -2672,4 +2673,13 @@
             return ++mId;
         }
     }
+
+    /**
+     * Returns this handler, ONLY FOR TESTING.
+     * @hide
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
 }
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 949f7b7..49c3a72 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -391,6 +391,20 @@
         return mCurrentThreadId.get();
     }
 
+    /**
+     * @return A String representation of the active sessions at the time that this method is
+     * called.
+     */
+    @VisibleForTesting
+    public synchronized String printActiveSessions() {
+        StringBuilder message = new StringBuilder();
+        for (ConcurrentHashMap.Entry<Integer, Session> entry : mSessionMapper.entrySet()) {
+            message.append(entry.getValue().printFullSessionTree());
+            message.append("\n");
+        }
+        return message.toString();
+    }
+
     @VisibleForTesting
     public synchronized void cleanupStaleSessions(long timeoutMs) {
         String logMessage = "Stale Sessions Cleaned:\n";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b5dbbe7..7079675 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -270,9 +270,6 @@
     private SubscriptionManager mSubscriptionManager;
     private TelephonyScanManager mTelephonyScanManager;
 
-    private static String multiSimConfig =
-            SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG);
-
     /** Enum indicating multisim variants
      *  DSDS - Dual SIM Dual Standby
      *  DSDA - Dual SIM Dual Active
@@ -432,8 +429,7 @@
     /** {@hide} */
     @UnsupportedAppUsage
     public boolean isMultiSimEnabled() {
-        return (multiSimConfig.equals("dsds") || multiSimConfig.equals("dsda") ||
-            multiSimConfig.equals("tsts"));
+        return getPhoneCount() > 1;
     }
 
     //
@@ -6625,11 +6621,7 @@
     public int getSimCount() {
         // FIXME Need to get it from Telephony Dev Controller when that gets implemented!
         // and then this method shouldn't be used at all!
-        if(isMultiSimEnabled()) {
-            return getPhoneCount();
-        } else {
-            return 1;
-        }
+        return getPhoneCount();
     }
 
     /**
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 7ffa5ff..137fbd6 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -246,6 +246,36 @@
   Printer* printer_;
 };
 
+std::string OverlayablePoliciesToString(OverlayableItem::PolicyFlags policies) {
+  static const std::map<OverlayableItem::PolicyFlags, std::string> kFlagToString = {
+    {OverlayableItem::kPublic, "public"},
+    {OverlayableItem::kSystem, "system"},
+    {OverlayableItem::kVendor, "vendor"},
+    {OverlayableItem::kProduct, "product"},
+    {OverlayableItem::kSignature, "signature"},
+    {OverlayableItem::kOdm, "odm"},
+    {OverlayableItem::kOem, "oem"},
+  };
+  std::string str;
+  for (auto const& policy : kFlagToString) {
+    if ((policies & policy.first) != policy.first) {
+      continue;
+    }
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(policy.second);
+    policies &= ~policy.first;
+  }
+  if (policies != 0) {
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(StringPrintf("0x%08x", policies));
+  }
+  return !str.empty() ? str : "none";
+}
+
 }  // namespace
 
 void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -312,6 +342,10 @@
             break;
         }
 
+        if (entry->overlayable_item) {
+          printer->Print(" OVERLAYABLE");
+        }
+
         printer->Println();
 
         if (options.show_values) {
@@ -525,4 +559,62 @@
   doc.root->Accept(&xml_visitor);
 }
 
+struct DumpOverlayableEntry {
+  std::string overlayable_section;
+  std::string policy_subsection;
+  std::string resource_name;
+};
+
+void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) {
+  std::vector<DumpOverlayableEntry> items;
+  for (const auto& package : table.packages) {
+    for (const auto& type : package->types) {
+      for (const auto& entry : type->entries) {
+        if (entry->overlayable_item) {
+          const auto& overlayable_item = entry->overlayable_item.value();
+          const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")",
+              overlayable_item.overlayable->name.c_str(),
+              overlayable_item.overlayable->actor.c_str());
+          const auto policy_subsection = StringPrintf(R"(policies="%s")",
+              OverlayablePoliciesToString(overlayable_item.policies).c_str());
+          const auto value =
+            StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
+          items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
+        }
+      }
+    }
+  }
+
+  std::sort(items.begin(), items.end(),
+      [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) {
+        if (a.overlayable_section != b.overlayable_section) {
+          return a.overlayable_section < b.overlayable_section;
+        }
+        if (a.policy_subsection != b.policy_subsection) {
+          return a.policy_subsection < b.policy_subsection;
+        }
+        return a.resource_name < b.resource_name;
+      });
+
+  std::string last_overlayable_section;
+  std::string last_policy_subsection;
+  for (const auto& item : items) {
+    if (last_overlayable_section != item.overlayable_section) {
+      printer->Println(item.overlayable_section);
+      last_overlayable_section = item.overlayable_section;
+    }
+    if (last_policy_subsection != item.policy_subsection) {
+      printer->Indent();
+      printer->Println(item.policy_subsection);
+      last_policy_subsection = item.policy_subsection;
+      printer->Undent();
+    }
+    printer->Indent();
+    printer->Indent();
+    printer->Println(item.resource_name);
+    printer->Undent();
+    printer->Undent();
+  }
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index a43197c..9443d60 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -39,6 +39,7 @@
   static void DumpHex(const void* data, size_t len);
   static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
   static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
+  static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 429aff1..3982d12 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -394,6 +394,17 @@
   return 0;
 }
 
+int DumpOverlayableCommand::Dump(LoadedApk* apk) {
+  ResourceTable* table = apk->GetResourceTable();
+  if (!table) {
+    GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table");
+    return 1;
+  }
+
+  Debug::DumpOverlayable(*table, GetPrinter());
+  return 0;
+}
+
 const char DumpBadgerCommand::kBadgerData[2925] = {
     32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
     32,  32,  32,  32,  32,  32,  95,  46,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 7ded9bc..cd51f7a 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -240,6 +240,16 @@
   std::vector<std::string> files_;
 };
 
+class DumpOverlayableCommand : public DumpApkCommand {
+ public:
+  explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag)
+      : DumpApkCommand("overlayable", printer, diag) {
+    SetDescription("Print the <overlayable> resources of an APK.");
+  }
+
+  int Dump(LoadedApk* apk) override;
+};
+
 /** The default dump command. Performs no action because a subcommand is required. */
 class DumpCommand : public Command {
  public:
@@ -255,8 +265,8 @@
     AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
+    AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
-    // TODO(b/120609160): Add aapt2 overlayable dump command
   }
 
   int Action(const std::vector<std::string>& args) override {