Merge "RESTRICT AUTOMERGE Fix detect biometric logic." into tm-qpr-dev
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 14d0a56..9d624b6 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3517,7 +3517,7 @@
      * <p>An array of mandatory stream combinations which are applicable when device support the
      * 10-bit output capability
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
-     * This is an app-readable conversion of the maximum resolution mandatory stream combination
+     * This is an app-readable conversion of the 10 bit output mandatory stream combination
      * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
@@ -3542,8 +3542,8 @@
     /**
      * <p>An array of mandatory stream combinations which are applicable when device lists
      * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
-     * This is an app-readable conversion of the maximum resolution mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
+     * This is an app-readable conversion of the preview stabilization mandatory stream
+     * combination {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index c67a560..7055c9c 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1000,24 +1000,25 @@
      * camera's crop region is set to maximum size, the FOV of the physical streams for the
      * ultrawide lens will be the same as the logical stream, by making the crop region
      * smaller than its active array size to compensate for the smaller focal length.</p>
-     * <p>There are two ways for the application to capture RAW images from a logical camera
-     * with RAW capability:</p>
+     * <p>For a logical camera, typically the underlying physical cameras have different RAW
+     * capabilities (such as resolution or CFA pattern). There are two ways for the
+     * application to capture RAW images from the logical camera:</p>
      * <ul>
-     * <li>Because the underlying physical cameras may have different RAW capabilities (such
-     * as resolution or CFA pattern), to maintain backward compatibility, when a RAW stream
-     * is configured, the camera device makes sure the default active physical camera remains
-     * active and does not switch to other physical cameras. (One exception is that, if the
-     * logical camera consists of identical image sensors and advertises multiple focalLength
-     * due to different lenses, the camera device may generate RAW images from different
-     * physical cameras based on the focalLength being set by the application.) This
-     * backward-compatible approach usually results in loss of optical zoom, to telephoto
-     * lens or to ultrawide lens.</li>
-     * <li>Alternatively, to take advantage of the full zoomRatio range of the logical camera,
-     * the application should use {@link android.hardware.camera2.MultiResolutionImageReader }
-     * to capture RAW images from the currently active physical camera. Because different
-     * physical camera may have different RAW characteristics, the application needs to use
-     * the characteristics and result metadata of the active physical camera for the
-     * relevant RAW metadata.</li>
+     * <li>If the logical camera has RAW capability, the application can create and use RAW
+     * streams in the same way as before. In case a RAW stream is configured, to maintain
+     * backward compatibility, the camera device makes sure the default active physical
+     * camera remains active and does not switch to other physical cameras. (One exception
+     * is that, if the logical camera consists of identical image sensors and advertises
+     * multiple focalLength due to different lenses, the camera device may generate RAW
+     * images from different physical cameras based on the focalLength being set by the
+     * application.) This backward-compatible approach usually results in loss of optical
+     * zoom, to telephoto lens or to ultrawide lens.</li>
+     * <li>Alternatively, if supported by the device,
+     * {@link android.hardware.camera2.MultiResolutionImageReader }
+     * can be used to capture RAW images from one of the underlying physical cameras (
+     * depending on current zoom level). Because different physical cameras may have
+     * different RAW characteristics, the application needs to use the characteristics
+     * and result metadata of the active physical camera for the relevant RAW metadata.</li>
      * </ul>
      * <p>The capture request and result metadata tags required for backward compatible camera
      * functionalities will be solely based on the logical camera capability. On the other
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 953f17a..f9e8411 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9883,9 +9883,12 @@
     }
 
     void checkThread() {
-        if (mThread != Thread.currentThread()) {
+        Thread current = Thread.currentThread();
+        if (mThread != current) {
             throw new CalledFromWrongThreadException(
-                    "Only the original thread that created a view hierarchy can touch its views.");
+                    "Only the original thread that created a view hierarchy can touch its views."
+                            + " Expected: " + mThread.getName()
+                            + " Calling: " + current.getName());
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 1239cdc..4578523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -194,7 +194,8 @@
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController) {
+            Optional<DesktopTasksController> desktopTasksController,
+            Optional<SplitScreenController> splitScreenController) {
         if (DesktopModeStatus.isAnyEnabled()) {
             return new DesktopModeWindowDecorViewModel(
                     context,
@@ -204,7 +205,8 @@
                     displayController,
                     syncQueue,
                     desktopModeController,
-                    desktopTasksController);
+                    desktopTasksController,
+                    splitScreenController);
         }
         return new CaptionWindowDecorViewModel(
                     context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index b59fe18..4cfaae6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -36,6 +36,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import android.content.ClipDescription;
+import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -58,9 +59,9 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalMainThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -70,7 +71,7 @@
  * Handles the global drag and drop handling for the Shell.
  */
 public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
-        View.OnDragListener, ConfigurationChangeListener {
+        View.OnDragListener, ComponentCallbacks2 {
 
     private static final String TAG = DragAndDropController.class.getSimpleName();
 
@@ -119,7 +120,6 @@
         mMainExecutor.executeDelayed(() -> {
             mDisplayController.addDisplayWindowListener(this);
         }, 0);
-        mShellController.addConfigurationChangeListener(this);
     }
 
     /**
@@ -180,6 +180,7 @@
         try {
             wm.addView(rootView, layoutParams);
             addDisplayDropTarget(displayId, context, wm, rootView, dragLayout);
+            context.registerComponentCallbacks(this);
         } catch (WindowManager.InvalidDisplayException e) {
             Slog.w(TAG, "Unable to add view for display id: " + displayId);
         }
@@ -209,6 +210,7 @@
         if (pd == null) {
             return;
         }
+        pd.context.unregisterComponentCallbacks(this);
         pd.wm.removeViewImmediate(pd.rootView);
         mDisplayDropTargets.remove(displayId);
     }
@@ -328,18 +330,29 @@
         return mimeTypes;
     }
 
-    @Override
-    public void onThemeChanged() {
-        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
-            mDisplayDropTargets.get(i).dragLayout.onThemeChange();
-        }
-    }
-
+    // Note: Component callbacks are always called on the main thread of the process
+    @ExternalMainThread
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
-            mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig);
-        }
+        mMainExecutor.execute(() -> {
+            for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+                mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig);
+            }
+        });
+    }
+
+    // Note: Component callbacks are always called on the main thread of the process
+    @ExternalMainThread
+    @Override
+    public void onTrimMemory(int level) {
+        // Do nothing
+    }
+
+    // Note: Component callbacks are always called on the main thread of the process
+    @ExternalMainThread
+    @Override
+    public void onLowMemory() {
+        // Do nothing
     }
 
     private static class PerDisplay {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 44fd8ee..fe42822a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -18,6 +18,8 @@
 
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -72,6 +74,7 @@
     private final SplitScreenController mSplitScreenController;
     private final IconProvider mIconProvider;
     private final StatusBarManager mStatusBarManager;
+    private final Configuration mLastConfiguration = new Configuration();
 
     private DragAndDropPolicy.Target mCurrentTarget = null;
     private DropZoneView mDropZoneView1;
@@ -92,6 +95,7 @@
         mIconProvider = iconProvider;
         mPolicy = new DragAndDropPolicy(context, splitScreenController);
         mStatusBarManager = context.getSystemService(StatusBarManager.class);
+        mLastConfiguration.setTo(context.getResources().getConfiguration());
 
         mDisplayMargin = context.getResources().getDimensionPixelSize(
                 R.dimen.drop_layout_display_margin);
@@ -132,11 +136,6 @@
         return super.onApplyWindowInsets(insets);
     }
 
-    public void onThemeChange() {
-        mDropZoneView1.onThemeChange();
-        mDropZoneView2.onThemeChange();
-    }
-
     public void onConfigChanged(Configuration newConfig) {
         if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
                 && getOrientation() != HORIZONTAL) {
@@ -147,6 +146,15 @@
             setOrientation(LinearLayout.VERTICAL);
             updateContainerMargins(newConfig.orientation);
         }
+
+        final int diff = newConfig.diff(mLastConfiguration);
+        final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
+                || (diff & CONFIG_UI_MODE) != 0;
+        if (themeChanged) {
+            mDropZoneView1.onThemeChange();
+            mDropZoneView2.onThemeChange();
+        }
+        mLastConfiguration.setTo(newConfig);
     }
 
     private void updateContainerMarginsForSingleTask() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c7ad4fd..94b9e90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -422,6 +422,11 @@
         mStageCoordinator.goToFullscreenFromSplit();
     }
 
+    /** Move the specified task to fullscreen, regardless of focus state. */
+    public void moveTaskToFullscreen(int taskId) {
+        mStageCoordinator.moveTaskToFullscreen(taskId);
+    }
+
     public boolean isLaunchToSplit(TaskInfo taskInfo) {
         return mStageCoordinator.isLaunchToSplit(taskInfo);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index f102f8e..a673384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2390,6 +2390,20 @@
         mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
     }
 
+    /** Move the specified task to fullscreen, regardless of focus state. */
+    public void moveTaskToFullscreen(int taskId) {
+        boolean leftOrTop;
+        if (mMainStage.containsTask(taskId)) {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+        } else if (mSideStage.containsTask(taskId)) {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        } else {
+            return;
+        }
+        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+
+    }
+
     boolean isLaunchToSplit(TaskInfo taskInfo) {
         return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 8dfa18c..c517f7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -20,10 +20,14 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
 import android.content.Context;
+import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -37,7 +41,6 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
 
 import androidx.annotation.Nullable;
 
@@ -50,6 +53,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.util.Optional;
 
@@ -80,6 +84,8 @@
     private final InputMonitorFactory mInputMonitorFactory;
     private TaskOperations mTaskOperations;
 
+    private Optional<SplitScreenController> mSplitScreenController;
+
     public DesktopModeWindowDecorViewModel(
             Context context,
             Handler mainHandler,
@@ -88,7 +94,8 @@
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController) {
+            Optional<DesktopTasksController> desktopTasksController,
+            Optional<SplitScreenController> splitScreenController) {
         this(
                 context,
                 mainHandler,
@@ -98,6 +105,7 @@
                 syncQueue,
                 desktopModeController,
                 desktopTasksController,
+                splitScreenController,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory());
     }
@@ -112,6 +120,7 @@
             SyncTransactionQueue syncQueue,
             Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
+            Optional<SplitScreenController> splitScreenController,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory) {
         mContext = context;
@@ -120,6 +129,7 @@
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
+        mSplitScreenController = splitScreenController;
         mSyncQueue = syncQueue;
         mDesktopModeController = desktopModeController;
         mDesktopTasksController = desktopTasksController;
@@ -230,6 +240,15 @@
             final int id = v.getId();
             if (id == R.id.close_window || id == R.id.close_button) {
                 mTaskOperations.closeTask(mTaskToken);
+                if (mSplitScreenController.isPresent()
+                        && mSplitScreenController.get().isSplitScreenVisible()) {
+                    int remainingTaskPosition = mTaskId == mSplitScreenController.get()
+                            .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
+                            ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
+                            .getTaskInfo(remainingTaskPosition);
+                    mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
+                }
             } else if (id == R.id.back_button) {
                 mTaskOperations.injectBackKey();
             } else if (id == R.id.caption_handle) {
@@ -261,9 +280,6 @@
                     if (taskInfo.isFocused) {
                         return mDragDetector.isDragEvent();
                     }
-                    final WindowContainerTransaction wct = new WindowContainerTransaction();
-                    wct.reorder(mTaskToken, true /* onTop */);
-                    mSyncQueue.queue(wct);
                     return false;
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
@@ -401,14 +417,14 @@
      * @param ev the {@link MotionEvent} received by {@link EventReceiver}
      */
     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
+        final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
         if (DesktopModeStatus.isProto2Enabled()) {
-            final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
-            if (focusedDecor == null
-                    || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
-                handleCaptionThroughStatusBar(ev);
+            if (relevantDecor == null
+                    || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                handleCaptionThroughStatusBar(ev, relevantDecor);
             }
         }
-        handleEventOutsideFocusedCaption(ev);
+        handleEventOutsideFocusedCaption(ev, relevantDecor);
         // Prevent status bar from reacting to a caption drag.
         if (DesktopModeStatus.isProto2Enabled()) {
             if (mTransitionDragActive) {
@@ -422,16 +438,16 @@
     }
 
     // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
-    private void handleEventOutsideFocusedCaption(MotionEvent ev) {
+    private void handleEventOutsideFocusedCaption(MotionEvent ev,
+            DesktopModeWindowDecoration relevantDecor) {
         final int action = ev.getActionMasked();
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
-            if (focusedDecor == null) {
+            if (relevantDecor == null) {
                 return;
             }
 
             if (!mTransitionDragActive) {
-                focusedDecor.closeHandleMenuIfNeeded(ev);
+                relevantDecor.closeHandleMenuIfNeeded(ev);
             }
         }
     }
@@ -441,39 +457,38 @@
      * Perform caption actions if not able to through normal means.
      * Turn on desktop mode if handle is dragged below status bar.
      */
-    private void handleCaptionThroughStatusBar(MotionEvent ev) {
+    private void handleCaptionThroughStatusBar(MotionEvent ev,
+            DesktopModeWindowDecoration relevantDecor) {
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 // Begin drag through status bar if applicable.
-                final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
-                if (focusedDecor != null) {
+                if (relevantDecor != null) {
                     boolean dragFromStatusBarAllowed = false;
                     if (DesktopModeStatus.isProto2Enabled()) {
                         // In proto2 any full screen task can be dragged to freeform
-                        dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
+                        dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
                                 == WINDOWING_MODE_FULLSCREEN;
                     }
 
-                    if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
+                    if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
                         mTransitionDragActive = true;
                     }
                 }
                 break;
             }
             case MotionEvent.ACTION_UP: {
-                final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
-                if (focusedDecor == null) {
+                if (relevantDecor == null) {
                     mTransitionDragActive = false;
                     return;
                 }
                 if (mTransitionDragActive) {
                     mTransitionDragActive = false;
                     final int statusBarHeight = mDisplayController
-                            .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+                            .getDisplayLayout(relevantDecor.mTaskInfo.displayId).stableInsets().top;
                     if (ev.getY() > statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
                             mDesktopTasksController.ifPresent(
-                                    c -> c.moveToDesktop(focusedDecor.mTaskInfo));
+                                    c -> c.moveToDesktop(relevantDecor.mTaskInfo));
                         } else if (DesktopModeStatus.isProto1Enabled()) {
                             mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
@@ -481,7 +496,7 @@
                         return;
                     }
                 }
-                focusedDecor.checkClickEvent(ev);
+                relevantDecor.checkClickEvent(ev);
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
@@ -491,6 +506,38 @@
     }
 
     @Nullable
+    private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
+        if (mSplitScreenController.isPresent()
+                && mSplitScreenController.get().isSplitScreenVisible()) {
+            // We can't look at focused task here as only one task will have focus.
+            return getSplitScreenDecor(ev);
+        } else {
+            return getFocusedDecor();
+        }
+    }
+
+    @Nullable
+    private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
+        ActivityManager.RunningTaskInfo topOrLeftTask =
+                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+        ActivityManager.RunningTaskInfo bottomOrRightTask =
+                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
+                .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
+            return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
+        } else if (bottomOrRightTask != null && bottomOrRightTask.getConfiguration()
+                .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
+            Rect bottomOrRightBounds = bottomOrRightTask.getConfiguration().windowConfiguration
+                    .getBounds();
+            ev.offsetLocation(-bottomOrRightBounds.left, -bottomOrRightBounds.top);
+            return mWindowDecorByTaskId.get(bottomOrRightTask.taskId);
+        } else {
+            return null;
+        }
+
+    }
+
+    @Nullable
     private DesktopModeWindowDecoration getFocusedDecor() {
         final int size = mWindowDecorByTaskId.size();
         DesktopModeWindowDecoration focusedDecor = null;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index b6dbcf2..523cb66 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -48,7 +48,6 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -82,7 +81,7 @@
     @Mock
     private ShellExecutor mMainExecutor;
     @Mock
-    private SplitScreenController mSplitScreenController;
+    private WindowManager mWindowManager;
 
     private DragAndDropController mController;
 
@@ -100,11 +99,6 @@
     }
 
     @Test
-    public void instantiateController_registerConfigChangeListener() {
-        verify(mShellController, times(1)).addConfigurationChangeListener(any());
-    }
-
-    @Test
     public void testIgnoreNonDefaultDisplays() {
         final int nonDefaultDisplayId = 12345;
         final View dragLayout = mock(View.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 3550721..1d1aa79 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -49,6 +49,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,6 +74,7 @@
     @Mock private Choreographer mMainChoreographer;
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private DisplayController mDisplayController;
+    @Mock private SplitScreenController mSplitScreenController;
     @Mock private SyncTransactionQueue mSyncQueue;
     @Mock private DesktopModeController mDesktopModeController;
     @Mock private DesktopTasksController mDesktopTasksController;
@@ -98,6 +100,7 @@
                 mSyncQueue,
                 Optional.of(mDesktopModeController),
                 Optional.of(mDesktopTasksController),
+                Optional.of(mSplitScreenController),
                 mDesktopModeWindowDecorFactory,
                 mMockInputMonitorFactory
             );
diff --git a/packages/AppPredictionLib/Android.bp b/packages/AppPredictionLib/Android.bp
index 5a68fdc..31c1936 100644
--- a/packages/AppPredictionLib/Android.bp
+++ b/packages/AppPredictionLib/Android.bp
@@ -25,7 +25,7 @@
     name: "app_prediction",
 
     sdk_version: "system_current",
-    min_sdk_version: "system_current",
+    min_sdk_version: "current",
 
     srcs: [
         "src/**/*.java",
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 2614644..688fc72 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -55,6 +55,7 @@
         public ComponentName settingsComponentName;
         public CharSequence description;
         public Drawable previewImage;
+        public boolean supportsComplications = false;
 
         @Override
         public String toString() {
@@ -175,6 +176,7 @@
             if (dreamMetadata != null) {
                 dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
                 dreamInfo.previewImage = dreamMetadata.previewImage;
+                dreamInfo.supportsComplications = dreamMetadata.showComplications;
             }
             dreamInfos.add(dreamInfo);
         }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index ab36d58..00c0a0b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -38,6 +38,7 @@
 
 private val TAG = ClockRegistry::class.simpleName!!
 private const val DEBUG = true
+private val KEY_TIMESTAMP = "appliedTimestamp"
 
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
@@ -134,9 +135,9 @@
         assertNotMainThread()
 
         try {
-            value?._applied_timestamp = System.currentTimeMillis()
-            val json = ClockSettings.serialize(value)
+            value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis())
 
+            val json = ClockSettings.serialize(value)
             if (handleAllUsers) {
                 Settings.Secure.putStringForUser(
                     context.contentResolver,
@@ -172,7 +173,7 @@
         clockChangeListeners.forEach(func)
     }
 
-    private fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
+    public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
         scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) }
     }
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 54c837f..babe5700 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -192,12 +192,13 @@
     val clockId: ClockId? = null,
     val seedColor: Int? = null,
 ) {
-    var _applied_timestamp: Long? = null
+    // Exclude metadata from equality checks
+    var metadata: JSONObject = JSONObject()
 
     companion object {
         private val KEY_CLOCK_ID = "clockId"
         private val KEY_SEED_COLOR = "seedColor"
-        private val KEY_TIMESTAMP = "_applied_timestamp"
+        private val KEY_METADATA = "metadata"
 
         fun serialize(setting: ClockSettings?): String {
             if (setting == null) {
@@ -207,7 +208,7 @@
             return JSONObject()
                 .put(KEY_CLOCK_ID, setting.clockId)
                 .put(KEY_SEED_COLOR, setting.seedColor)
-                .put(KEY_TIMESTAMP, setting._applied_timestamp)
+                .put(KEY_METADATA, setting.metadata)
                 .toString()
         }
 
@@ -219,11 +220,11 @@
             val json = JSONObject(jsonStr)
             val result =
                 ClockSettings(
-                    json.getString(KEY_CLOCK_ID),
+                    if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null,
                     if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
                 )
-            if (!json.isNull(KEY_TIMESTAMP)) {
-                result._applied_timestamp = json.getLong(KEY_TIMESTAMP)
+            if (!json.isNull(KEY_METADATA)) {
+                result.metadata = json.getJSONObject(KEY_METADATA)
             }
             return result
         }
diff --git a/packages/SystemUI/res-keyguard/drawable-mdpi/ic_lockscreen_sim.png b/packages/SystemUI/res-keyguard/drawable-mdpi/ic_lockscreen_sim.png
deleted file mode 100644
index 2e259c3..0000000
--- a/packages/SystemUI/res-keyguard/drawable-mdpi/ic_lockscreen_sim.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res-keyguard/drawable-xhdpi/ic_lockscreen_sim.png b/packages/SystemUI/res-keyguard/drawable-xhdpi/ic_lockscreen_sim.png
deleted file mode 100644
index f4de96a..0000000
--- a/packages/SystemUI/res-keyguard/drawable-xhdpi/ic_lockscreen_sim.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res-keyguard/drawable-hdpi/ic_lockscreen_sim.png b/packages/SystemUI/res-keyguard/drawable/ic_lockscreen_sim.png
similarity index 100%
rename from packages/SystemUI/res-keyguard/drawable-hdpi/ic_lockscreen_sim.png
rename to packages/SystemUI/res-keyguard/drawable/ic_lockscreen_sim.png
Binary files differ
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
index 411fea5..48769fd 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
@@ -14,10 +14,12 @@
   ~ limitations under the License
   -->
 
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
     <TextView
         android:id="@+id/digit_text"
         style="@style/Widget.TextView.NumPadKey.Digit"
+        android:autoSizeMaxTextSize="32sp"
+        android:autoSizeTextType="uniform"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 7db0fe9..728d861 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
 **
 ** Copyright 2012, The Android Open Source Project
 **
@@ -17,185 +16,185 @@
 */
 -->
 <!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. -->
-<com.android.keyguard.KeyguardSimPinView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/res-auto"
-        android:id="@+id/keyguard_sim_pin_view"
-        android:orientation="vertical"
+<com.android.keyguard.KeyguardSimPinView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_sim_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+    android:layout_gravity="center_horizontal|bottom">
+    <include layout="@layout/keyguard_bouncer_message_area"/>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        androidprv:layout_maxWidth="@dimen/keyguard_security_width"
-        android:layout_gravity="center_horizontal|bottom">
-    <include layout="@layout/keyguard_bouncer_message_area" />
-    <Space
-          android:layout_width="match_parent"
-          android:layout_height="0dp"
-          android:layout_weight="1" />
-    <ImageView
-            android:id="@+id/keyguard_sim"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:tint="@color/background_protected"
-            android:src="@drawable/ic_lockscreen_sim"/>
-    <LinearLayout
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:layoutDirection="ltr">
+        <LinearLayout
+            android:id="@+id/pin_area"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:gravity="center"
-            android:layoutDirection="ltr"
-            >
-        <include layout="@layout/keyguard_esim_area"
-             android:id="@+id/keyguard_esim_area"
-             android:layout_width="wrap_content"
-             android:layout_height="wrap_content" />
-        <RelativeLayout
-                android:id="@+id/row0"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingBottom="4dp"
-                >
+            android:gravity="center_horizontal"
+            android:paddingTop="@dimen/num_pad_entry_row_margin_bottom"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/flow1"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent">
+
+            <ImageView
+                android:id="@+id/keyguard_sim"
+                android:layout_width="40dp"
+                android:layout_height="40dp"
+                android:layout_gravity="center_horizontal"
+                android:src="@drawable/ic_lockscreen_sim"
+                app:tint="@color/background_protected" />
+
+            <include
+                android:id="@+id/keyguard_esim_area"
+                layout="@layout/keyguard_esim_area"
+                android:layout_gravity="center_horizontal"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
             <com.android.keyguard.PasswordTextView
                 android:id="@+id/simPinEntry"
                 style="@style/Widget.TextView.Password"
                 android:layout_width="@dimen/keyguard_security_width"
                 android:layout_height="@dimen/keyguard_password_height"
-                android:layout_centerHorizontal="true"
-                android:layout_marginRight="72dp"
                 android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
-                android:gravity="center"
+                android:layout_gravity="center_horizontal"
                 androidprv:scaledTextSize="@integer/scaled_password_text_size" />
-        </RelativeLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
-                >
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key1"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="1"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key2"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="2"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key3"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="3"
-                    />
         </LinearLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
-                >
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key4"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="4"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key5"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="5"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key6"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="6"
-                    />
-        </LinearLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
-                >
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key7"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="7"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key8"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="8"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key9"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="9"
-                    />
-        </LinearLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                >
-            <com.android.keyguard.NumPadButton
-                    android:id="@+id/delete_button"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    android:contentDescription="@string/keyboardview_keycode_delete"
-                    style="@style/NumPadKey.Delete"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key0"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/simPinEntry"
-                    androidprv:digit="0"
-                    />
-            <com.android.keyguard.NumPadButton
-                    android:id="@+id/key_enter"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    style="@style/NumPadKey.Enter"
-                    android:contentDescription="@string/keyboardview_keycode_enter"
-                    />
-        </LinearLayout>
-    </LinearLayout>
-    <include layout="@layout/keyguard_eca"
-             android:id="@+id/keyguard_selector_fade_container"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content"
-             android:orientation="vertical"
-             android:layout_gravity="bottom|center_horizontal"
-             android:layout_marginTop="@dimen/keyguard_eca_top_margin"
-             android:layout_marginBottom="2dp"
-             android:gravity="center_horizontal"/>
+
+        <androidx.constraintlayout.helper.widget.Flow
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+            androidprv:flow_verticalBias="1.0"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_area" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/simPinEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/simPinEntry" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <include
+        android:id="@+id/keyguard_selector_fade_container"
+        layout="@layout/keyguard_eca"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_marginBottom="2dp"
+        android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+        android:gravity="center_horizontal"
+        android:orientation="vertical" />
 </com.android.keyguard.KeyguardSimPinView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index 422bd4c..7e24d12 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -21,6 +21,7 @@
 <com.android.keyguard.KeyguardSimPukView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
         android:id="@+id/keyguard_sim_puk_view"
         android:orientation="vertical"
         android:layout_width="match_parent"
@@ -29,173 +30,165 @@
         android:layout_gravity="center_horizontal|bottom">
     <include layout="@layout/keyguard_bouncer_message_area"/>
 
-    <Space
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:layout_weight="1" />
-
-    <ImageView
-            android:id="@+id/keyguard_sim"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:tint="@color/background_protected"
-            android:src="@drawable/ic_lockscreen_sim"/>
-
-    <LinearLayout
+        android:layout_weight="1"
+        android:layoutDirection="ltr">
+        <LinearLayout
+            android:id="@+id/pin_area"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:gravity="center"
-            android:layoutDirection="ltr"
-            >
-        <include layout="@layout/keyguard_esim_area"
-            android:id="@+id/keyguard_esim_area"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
+            android:gravity="center_horizontal"
+            android:paddingTop="@dimen/num_pad_entry_row_margin_bottom"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/flow1"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent">
 
-        <RelativeLayout
-                android:id="@+id/row0"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingBottom="4dp"
-                >
+            <ImageView
+                android:id="@+id/keyguard_sim"
+                android:layout_width="40dp"
+                android:layout_height="40dp"
+                android:layout_gravity="center_horizontal"
+                android:src="@drawable/ic_lockscreen_sim"
+                app:tint="@color/background_protected" />
+
+            <include
+                android:id="@+id/keyguard_esim_area"
+                layout="@layout/keyguard_esim_area"
+                android:layout_gravity="center_horizontal"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
 
             <com.android.keyguard.PasswordTextView
                 android:id="@+id/pukEntry"
                 style="@style/Widget.TextView.Password"
                 android:layout_width="@dimen/keyguard_security_width"
                 android:layout_height="@dimen/keyguard_password_height"
-                android:layout_centerHorizontal="true"
-                android:layout_marginRight="72dp"
-                android:contentDescription="@string/keyguard_accessibility_sim_puk_area"
-                android:gravity="center"
+                android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+                android:layout_gravity="center_horizontal"
                 androidprv:scaledTextSize="@integer/scaled_password_text_size" />
-        </RelativeLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
-                >
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key1"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="1"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key2"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="2"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key3"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="3"
-                    />
         </LinearLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
 
-                >
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key4"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="4"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key5"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="5"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key6"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="6"
-                    />
-        </LinearLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
-                >
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key7"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="7"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key8"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="8"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key9"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="9"
-                    />
-        </LinearLayout>
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_gravity="center_horizontal"
-                >
-            <com.android.keyguard.NumPadButton
-                    android:id="@+id/delete_button"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    android:contentDescription="@string/keyboardview_keycode_delete"
-                    style="@style/NumPadKey.Delete"
-                    />
-            <com.android.keyguard.NumPadKey
-                    android:id="@+id/key0"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="@dimen/num_pad_key_margin_end"
-                    androidprv:textView="@+id/pukEntry"
-                    androidprv:digit="0"
-                    />
-            <com.android.keyguard.NumPadButton
-                    android:id="@+id/key_enter"
-                    android:layout_width="@dimen/num_pad_key_width"
-                    android:layout_height="match_parent"
-                    style="@style/NumPadKey.Enter"
-                    android:contentDescription="@string/keyboardview_keycode_enter"
-                    />
-        </LinearLayout>
-    </LinearLayout>
+        <androidx.constraintlayout.helper.widget.Flow
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+            androidprv:flow_verticalBias="1.0"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_area" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pukEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pukEntry" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <include layout="@layout/keyguard_eca"
              android:id="@+id/keyguard_selector_fade_container"
diff --git a/packages/SystemUI/res/drawable/controls_panel_background.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml
index 9092877..fc108a5 100644
--- a/packages/SystemUI/res/drawable/controls_panel_background.xml
+++ b/packages/SystemUI/res/drawable/controls_panel_background.xml
@@ -18,5 +18,5 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="#1F1F1F" />
-    <corners android:radius="@dimen/notification_corner_radius" />
+    <corners android:radius="@dimen/controls_panel_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_edit_circle.xml b/packages/SystemUI/res/drawable/qs_footer_edit_circle.xml
new file mode 100644
index 0000000..2e29cae
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_edit_circle.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:inset="@dimen/qs_footer_action_inset">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+            <!-- properly into an app/dialog. -->
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
+            </shape>
+        </item>
+
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index e08e63b..fa70303 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -15,19 +15,11 @@
      limitations under the License.
 -->
 
-<FrameLayout
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/control_detail_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-
-    <LinearLayout
-        android:id="@+id/global_actions_controls"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:paddingHorizontal="@dimen/controls_padding_horizontal" />
-
-</FrameLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index aa211bf..71561c0 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -13,82 +13,94 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<merge
-    xmlns:android="http://schemas.android.com/apk/res/android">
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:orientation="vertical"
+    tools:parentTag="android.widget.LinearLayout">
 
-  <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:orientation="horizontal"
-      android:layout_marginBottom="@dimen/controls_header_bottom_margin">
-
-    <!-- make sure the header stays centered in the layout by adding a spacer -->
-    <Space
-        android:id="@+id/controls_spacer"
-        android:layout_width="@dimen/controls_header_menu_size"
-        android:layout_height="1dp"
-        android:visibility="gone" />
-
-    <ImageView
-        android:id="@+id/controls_close"
-        android:contentDescription="@string/accessibility_desc_close"
-        android:src="@drawable/ic_close"
-        android:background="?android:attr/selectableItemBackgroundBorderless"
-        android:tint="@color/control_primary_text"
-        android:layout_width="@dimen/controls_header_menu_size"
-        android:layout_height="@dimen/controls_header_menu_size"
-        android:padding="12dp"
-        android:visibility="gone" />
-    <!-- need to keep this outer view in order to have a correctly sized anchor
-         for the dropdown menu, as well as dropdown background in the right place -->
     <LinearLayout
-        android:id="@+id/controls_header"
-        android:orientation="horizontal"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:minHeight="48dp"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:gravity="center">
-      <TextView
-          style="@style/Control.Spinner.Header"
-          android:clickable="false"
-          android:id="@+id/app_or_structure_spinner"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_gravity="center" />
-    </LinearLayout>
-    <ImageView
-        android:id="@+id/controls_more"
-        android:src="@drawable/ic_more_vert"
-        android:layout_width="@dimen/controls_header_menu_size"
-        android:layout_height="@dimen/controls_header_menu_size"
-        android:padding="12dp"
-        android:tint="@color/control_more_vert"
-        android:layout_gravity="center"
-        android:contentDescription="@string/accessibility_menu"
-        android:background="?android:attr/selectableItemBackgroundBorderless" />
-  </LinearLayout>
+        android:paddingHorizontal="@dimen/controls_header_horizontal_padding"
+        android:layout_marginBottom="@dimen/controls_header_bottom_margin"
+        android:orientation="horizontal">
 
-  <ScrollView
+        <!-- make sure the header stays centered in the layout by adding a spacer -->
+        <Space
+            android:id="@+id/controls_spacer"
+            android:layout_width="@dimen/controls_header_menu_button_size"
+            android:layout_height="1dp"
+            android:visibility="gone" />
+
+        <ImageView
+            android:id="@+id/controls_close"
+            android:layout_width="@dimen/controls_header_menu_button_size"
+            android:layout_height="@dimen/controls_header_menu_button_size"
+            android:layout_gravity="center_vertical"
+            android:background="?android:attr/selectableItemBackgroundBorderless"
+            android:contentDescription="@string/accessibility_desc_close"
+            android:padding="12dp"
+            android:src="@drawable/ic_close"
+            android:tint="@color/control_primary_text"
+            android:visibility="gone"
+            tools:visibility="visible" />
+
+        <!-- need to keep this outer view in order to have a correctly sized anchor
+             for the dropdown menu, as well as dropdown background in the right place -->
+        <LinearLayout
+            android:id="@+id/controls_header"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:minHeight="48dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/app_or_structure_spinner"
+                style="@style/Control.Spinner.Header"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:clickable="false"
+                tools:text="Test app" />
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/controls_more"
+            android:layout_width="@dimen/controls_header_menu_button_size"
+            android:layout_height="@dimen/controls_header_menu_button_size"
+            android:layout_gravity="center_vertical"
+            android:background="?android:attr/selectableItemBackgroundBorderless"
+            android:contentDescription="@string/accessibility_menu"
+            android:padding="12dp"
+            android:src="@drawable/ic_more_vert"
+            android:tint="@color/control_more_vert" />
+    </LinearLayout>
+
+    <ScrollView
         android:id="@+id/controls_scroll_view"
         android:layout_width="match_parent"
         android:layout_height="0dp"
+        android:layout_marginHorizontal="@dimen/controls_content_margin_horizontal"
         android:layout_weight="1"
-        android:orientation="vertical"
         android:clipChildren="true"
+        android:orientation="vertical"
         android:paddingHorizontal="16dp"
         android:scrollbars="none">
-    <include layout="@layout/global_actions_controls_list_view" />
 
-  </ScrollView>
+        <include layout="@layout/global_actions_controls_list_view" />
 
-  <FrameLayout
-      android:id="@+id/controls_panel"
-      android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:layout_weight="1"
-      android:background="@drawable/controls_panel_background"
-      android:visibility="gone"
-      />
+    </ScrollView>
+
+    <FrameLayout
+        android:id="@+id/controls_panel"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginHorizontal="@dimen/controls_content_margin_horizontal"
+        android:layout_weight="1"
+        android:background="@drawable/controls_panel_background"
+        android:visibility="gone"
+        tools:visibility="visible" />
 </merge>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b1d3ed05..745cfc6 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -64,7 +64,7 @@
                     android:layout_width="@dimen/qs_footer_action_button_size"
                     android:layout_height="@dimen/qs_footer_action_button_size"
                     android:layout_gravity="center_vertical|end"
-                    android:background="?android:attr/selectableItemBackground"
+                    android:background="@drawable/qs_footer_edit_circle"
                     android:clickable="true"
                     android:contentDescription="@string/accessibility_quick_settings_edit"
                     android:focusable="true"
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index bc63c9f..4f38e60 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -64,5 +64,6 @@
 
     <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
 
-    <dimen name="controls_padding_horizontal">16dp</dimen>
+    <dimen name="controls_header_horizontal_padding">12dp</dimen>
+    <dimen name="controls_content_margin_horizontal">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7cd1470..59becc6 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -87,4 +87,7 @@
 
     <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
     <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+
+    <dimen name="controls_header_horizontal_padding">12dp</dimen>
+    <dimen name="controls_content_margin_horizontal">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 9ed9360..8583f05 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -37,6 +37,8 @@
     <dimen name="qs_media_rec_album_size">112dp</dimen>
     <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
 
+    <dimen name="controls_panel_corner_radius">40dp</dimen>
+
     <dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
 
     <!-- Roughly the same distance as media on LS to media on QS. We will translate by this value
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 8b41a44..9248d58 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -33,5 +33,7 @@
      side -->
     <dimen name="qs_tiles_page_horizontal_margin">60dp</dimen>
 
+    <dimen name="controls_panel_corner_radius">46dp</dimen>
+
     <dimen name="notification_section_divider_height">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 8f59df6..2086459 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -19,7 +19,8 @@
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_padding">1dp</dimen>
 
-    <dimen name="controls_padding_horizontal">40dp</dimen>
+    <dimen name="controls_header_horizontal_padding">28dp</dimen>
+    <dimen name="controls_content_margin_horizontal">40dp</dimen>
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f7310b0..bf949a0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1153,11 +1153,13 @@
 
     <!-- Home Controls -->
     <dimen name="controls_header_menu_size">48dp</dimen>
+    <dimen name="controls_header_menu_button_size">48dp</dimen>
     <dimen name="controls_header_bottom_margin">16dp</dimen>
+    <dimen name="controls_header_horizontal_padding">12dp</dimen>
     <dimen name="controls_header_app_icon_size">24dp</dimen>
     <dimen name="controls_top_margin">48dp</dimen>
-    <dimen name="controls_padding_horizontal">0dp</dimen>
-    <dimen name="control_header_text_size">20sp</dimen>
+    <dimen name="controls_content_margin_horizontal">0dp</dimen>
+    <dimen name="control_header_text_size">24sp</dimen>
     <dimen name="control_item_text_size">16sp</dimen>
     <dimen name="control_menu_item_text_size">16sp</dimen>
     <dimen name="control_menu_item_min_height">56dp</dimen>
@@ -1188,6 +1190,8 @@
     <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">1.0</item>
     <dimen name="controls_task_view_right_margin">0dp</dimen>
 
+    <dimen name="controls_panel_corner_radius">42dp</dimen>
+
     <!-- Home Controls activity view detail panel-->
     <dimen name="controls_activity_view_corner_radius">@*android:dimen/config_bottomDialogCornerRadius</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 464ce03..48f257a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2262,6 +2262,10 @@
          panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
     <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
 
+    <!-- Shows in a dialog presented to the user to authorize this app removal from a Device
+         controls panel [CHAR LIMIT=NONE] -->
+    <string name="controls_panel_remove_app_authorization">Remove controls for <xliff:g example="My app" id="appName">%s</xliff:g>?</string>
+
     <!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
     <string name="accessibility_control_favorite">Favorited</string>
     <!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
@@ -2302,6 +2306,8 @@
     <string name="controls_dialog_title">Add to device controls</string>
     <!-- Controls dialog add to favorites [CHAR LIMIT=40] -->
     <string name="controls_dialog_ok">Add</string>
+    <!-- Controls dialog remove app from a panel [CHAR LIMIT=40] -->
+    <string name="controls_dialog_remove">Remove</string>
     <!-- Controls dialog message. Indicates app that suggested this control [CHAR LIMIT=NONE] -->
     <string name="controls_dialog_message">Suggested by <xliff:g id="app" example="System UI">%s</xliff:g></string>
     <!-- Controls tile secondary label when device is locked and user does not want access to controls from lockscreen [CHAR LIMIT=20] -->
@@ -2419,6 +2425,8 @@
     <string name="controls_menu_edit">Edit controls</string>
     <!-- Controls menu, add another app [CHAR LIMIT=30] -->
     <string name="controls_menu_add_another_app">Add app</string>
+    <!-- Controls menu, remove app [CHAR_LIMIT=30] -->
+    <string name="controls_menu_remove">Remove app</string>
 
     <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] -->
     <string name="media_output_dialog_add_output">Add outputs</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index b927155..8690b36 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -62,7 +62,7 @@
      */
     public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
             int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
-            int taskbarSize, boolean isTablet,
+            int taskbarSize, boolean isLargeScreen,
             int currentRotation, boolean isRtl) {
         boolean isRotated = false;
         boolean isOrientationDifferent;
@@ -95,7 +95,7 @@
             canvasScreenRatio = (float) canvasWidth / screenWidthPx;
         }
         scaledTaskbarSize = taskbarSize * canvasScreenRatio;
-        thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+        thumbnailClipHint.bottom = isLargeScreen ? scaledTaskbarSize : 0;
 
         float scale = thumbnailData.scale;
         final float thumbnailScale;
@@ -103,7 +103,7 @@
         // Landscape vs portrait change.
         // Note: Disable rotation in grid layout.
         boolean windowingModeSupportsRotation =
-                thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet;
+                thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isLargeScreen;
         isOrientationDifferent = isOrientationChange(deltaRotate)
                 && windowingModeSupportsRotation;
         if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 77a13bd..751a3f8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -137,7 +137,7 @@
 
     /** @return whether or not {@param context} represents that of a large screen device or not */
     @TargetApi(Build.VERSION_CODES.R)
-    public static boolean isTablet(Context context) {
+    public static boolean isLargeScreen(Context context) {
         final WindowManager windowManager = context.getSystemService(WindowManager.class);
         final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index d221e22..a010c9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,6 +26,7 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -156,6 +157,15 @@
         // TODO: Remove this workaround by ensuring such a race condition never happens.
         mMainExecutor.executeDelayed(
                 this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
+        mView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+                if (!mKeyguardViewController.isBouncerShowing()) {
+                    mView.hideKeyboard();
+                }
+                return insets;
+            }
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6a28b5d..be01377 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -73,11 +73,9 @@
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.SuppressLint;
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AlarmManager;
-import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
@@ -104,7 +102,6 @@
 import android.nfc.NfcAdapter;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
@@ -175,6 +172,7 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -2203,7 +2201,7 @@
                         handleDevicePolicyManagerStateChanged(msg.arg1);
                         break;
                     case MSG_USER_SWITCHING:
-                        handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
+                        handleUserSwitching(msg.arg1, (CountDownLatch) msg.obj);
                         break;
                     case MSG_USER_SWITCH_COMPLETE:
                         handleUserSwitchComplete(msg.arg1);
@@ -2328,11 +2326,7 @@
                 mHandler, UserHandle.ALL);
 
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
+        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
 
         mTrustManager.registerTrustListener(this);
 
@@ -2468,17 +2462,17 @@
         return mIsFaceEnrolled;
     }
 
-    private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
+    private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
         @Override
-        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+        public void onUserChanging(int newUser, Context userContext, CountDownLatch latch) {
             mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHING,
-                    newUserId, 0, reply));
+                    newUser, 0, latch));
         }
 
         @Override
-        public void onUserSwitchComplete(int newUserId) {
+        public void onUserChanged(int newUser, Context userContext) {
             mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE,
-                    newUserId, 0));
+                    newUser, 0));
         }
     };
 
@@ -3191,7 +3185,7 @@
      * Handle {@link #MSG_USER_SWITCHING}
      */
     @VisibleForTesting
-    void handleUserSwitching(int userId, IRemoteCallback reply) {
+    void handleUserSwitching(int userId, CountDownLatch latch) {
         Assert.isMainThread();
         clearBiometricRecognized();
         mUserTrustIsUsuallyManaged.put(userId, mTrustManager.isTrustUsuallyManaged(userId));
@@ -3201,11 +3195,7 @@
                 cb.onUserSwitching(userId);
             }
         }
-        try {
-            reply.sendResult(null);
-        } catch (RemoteException e) {
-            mLogger.logException(e, "Ignored exception while userSwitching");
-        }
+        latch.countDown();
     }
 
     /**
@@ -3975,13 +3965,7 @@
             mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
         }
 
-        try {
-            ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
-        } catch (RemoteException e) {
-            mLogger.logException(
-                    e,
-                    "RemoteException onDestroy. cannot unregister userSwitchObserver");
-        }
+        mUserTracker.removeCallback(mUserChangedCallback);
 
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index b30a0e0..ad66909 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -51,7 +51,6 @@
     private float mStartRadius;
     private float mEndRadius;
     private int mHeight;
-    private boolean mInitialized;
 
     private static final int EXPAND_ANIMATION_MS = 100;
     private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -93,15 +92,15 @@
     }
 
     void onLayout(int height) {
+        boolean shouldUpdateHeight = height != mHeight;
         mHeight = height;
         mStartRadius = height / 2f;
         mEndRadius = height / 4f;
         mExpandAnimator.setFloatValues(mStartRadius, mEndRadius);
         mContractAnimator.setFloatValues(mEndRadius, mStartRadius);
         // Set initial corner radius.
-        if (!mInitialized) {
+        if (shouldUpdateHeight) {
             mBackground.setCornerRadius(mStartRadius);
-            mInitialized = true;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index cef415c..f6217f1 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -129,8 +129,8 @@
         AnimatorSet animatorSet = new AnimatorSet();
         animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
 
-        // For tablet docking animation, we don't play the background scrim.
-        if (!Utilities.isTablet(context)) {
+        // For large screens docking animation, we don't play the background scrim.
+        if (!Utilities.isLargeScreen(context)) {
             ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
                     "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
             scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 822190f..3555d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -166,6 +166,19 @@
     )
 
     /**
+     * Removes favorites for a given component
+     * @param componentName the name of the service that provides the [Control]
+     * @return true when favorites is scheduled for deletion
+     */
+    fun removeFavorites(componentName: ComponentName): Boolean
+
+    /**
+     * Checks if the favorites can be removed. You can't remove components from the preferred list.
+     * @param componentName the name of the service that provides the [Control]
+     */
+    fun canRemoveFavorites(componentName: ComponentName): Boolean
+
+    /**
      * Replaces the favorites for the given structure.
      *
      * Calling this method will eliminate the previous selection of favorites and replace it with a
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 278ee70..8547903 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -497,6 +497,21 @@
         }
     }
 
+    override fun canRemoveFavorites(componentName: ComponentName): Boolean =
+            !authorizedPanelsRepository.getPreferredPackages().contains(componentName.packageName)
+
+    override fun removeFavorites(componentName: ComponentName): Boolean {
+        if (!confirmAvailability()) return false
+        if (!canRemoveFavorites(componentName)) return false
+
+        executor.execute {
+            Favorites.removeStructures(componentName)
+            authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName))
+            persistenceWrapper.storeFavorites(Favorites.getAllStructures())
+        }
+        return true
+    }
+
     override fun replaceFavoritesForStructure(structureInfo: StructureInfo) {
         if (!confirmAvailability()) return
         executor.execute {
@@ -655,10 +670,11 @@
         return true
     }
 
-    fun removeStructures(componentName: ComponentName) {
+    fun removeStructures(componentName: ComponentName): Boolean {
         val newFavMap = favMap.toMutableMap()
-        newFavMap.remove(componentName)
+        val removed = newFavMap.remove(componentName) != null
         favMap = newFavMap
+        return removed
     }
 
     fun addFavorite(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index 3e672f3..ae9c37a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -26,6 +26,14 @@
     /** A set of package names that the user has previously authorized to show panels. */
     fun getAuthorizedPanels(): Set<String>
 
+    /** Preferred applications to query controls suggestions from */
+    fun getPreferredPackages(): Set<String>
+
     /** Adds [packageNames] to the set of packages that the user has authorized to show panels. */
     fun addAuthorizedPanels(packageNames: Set<String>)
+
+    /**
+     * Removes [packageNames] from the set of packages that the user has authorized to show panels.
+     */
+    fun removeAuthorizedPanels(packageNames: Set<String>)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index f7e43a7..e51e832 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -37,10 +37,20 @@
         return getAuthorizedPanelsInternal(instantiateSharedPrefs())
     }
 
+    override fun getPreferredPackages(): Set<String> =
+        context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
+
     override fun addAuthorizedPanels(packageNames: Set<String>) {
         addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
     }
 
+    override fun removeAuthorizedPanels(packageNames: Set<String>) {
+        with(instantiateSharedPrefs()) {
+            val currentSet = getAuthorizedPanelsInternal(this)
+            edit().putStringSet(KEY, currentSet - packageNames).apply()
+        }
+    }
+
     private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> {
         return sharedPreferences.getStringSet(KEY, emptySet())!!
     }
@@ -63,15 +73,7 @@
 
         // If we've never run this (i.e., the key doesn't exist), add the default packages
         if (sharedPref.getStringSet(KEY, null) == null) {
-            sharedPref
-                .edit()
-                .putStringSet(
-                    KEY,
-                    context.resources
-                        .getStringArray(R.array.config_controlsPreferredPackages)
-                        .toSet()
-                )
-                .apply()
+            sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
         }
         return sharedPref
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 3a3f9b4..bf0a692 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -68,7 +68,7 @@
 
         getLifecycle().addObserver(
             ControlsAnimations.observerForAnimations(
-                requireViewById<ViewGroup>(R.id.control_detail_root),
+                requireViewById(R.id.control_detail_root),
                 window,
                 intent,
                 !featureFlags.isEnabled(Flags.USE_APP_PANELS)
@@ -95,7 +95,7 @@
     override fun onStart() {
         super.onStart()
 
-        parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
+        parent = requireViewById(R.id.control_detail_root)
         parent.alpha = 0f
         if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
             controlsSettingsDialogManager.maybeShowDialog(this) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
new file mode 100644
index 0000000..d6cfb79
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.controls.ui
+
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.function.Consumer
+import javax.inject.Inject
+
+class ControlsDialogsFactory(private val internalDialogFactory: (Context) -> SystemUIDialog) {
+
+    @Inject constructor() : this({ SystemUIDialog(it) })
+
+    fun createRemoveAppDialog(
+        context: Context,
+        appName: CharSequence,
+        response: Consumer<Boolean>
+    ): Dialog {
+        val listener =
+            DialogInterface.OnClickListener { _, which ->
+                response.accept(which == DialogInterface.BUTTON_POSITIVE)
+            }
+        return internalDialogFactory(context).apply {
+            setTitle(context.getString(R.string.controls_panel_remove_app_authorization, appName))
+            setCanceledOnTouchOutside(true)
+            setOnCancelListener { response.accept(false) }
+            setPositiveButton(R.string.controls_dialog_remove, listener)
+            setNeutralButton(R.string.cancel, listener)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 9405c60..c61dad6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -21,6 +21,7 @@
 import android.animation.ObjectAnimator
 import android.app.Activity
 import android.app.ActivityOptions
+import android.app.Dialog
 import android.app.PendingIntent
 import android.content.ComponentName
 import android.content.Context
@@ -52,7 +53,6 @@
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
@@ -64,6 +64,7 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -83,7 +84,7 @@
 import dagger.Lazy
 import java.io.PrintWriter
 import java.text.Collator
-import java.util.Optional
+import java.util.*
 import java.util.function.Consumer
 import javax.inject.Inject
 
@@ -108,6 +109,7 @@
         private val controlsSettingsRepository: ControlsSettingsRepository,
         private val authorizedPanelsRepository: AuthorizedPanelsRepository,
         private val featureFlags: FeatureFlags,
+        private val dialogsFactory: ControlsDialogsFactory,
         dumpManager: DumpManager
 ) : ControlsUiController, Dumpable {
 
@@ -122,6 +124,7 @@
         private const val ADD_CONTROLS_ID = 1L
         private const val ADD_APP_ID = 2L
         private const val EDIT_CONTROLS_ID = 3L
+        private const val REMOVE_APP_ID = 4L
     }
 
     private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
@@ -151,6 +154,7 @@
 
     private var openAppIntent: Intent? = null
     private var overflowMenuAdapter: BaseAdapter? = null
+    private var removeAppDialog: Dialog? = null
 
     private val onSeedingComplete = Consumer<Boolean> {
         accepted ->
@@ -330,6 +334,31 @@
         }
     }
 
+    @VisibleForTesting
+    internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) {
+        removeAppDialog?.cancel()
+        removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) {
+            if (!controlsController.get().removeFavorites(componentName)) {
+                return@createRemoveAppDialog
+            }
+            if (
+                    sharedPreferences.getString(PREF_COMPONENT, "") ==
+                    componentName.flattenToString()
+            ) {
+                sharedPreferences
+                        .edit()
+                        .remove(PREF_COMPONENT)
+                        .remove(PREF_STRUCTURE_OR_APP_NAME)
+                        .remove(PREF_IS_PANEL)
+                        .commit()
+            }
+
+            allStructures = controlsController.get().getFavorites()
+            selectedItem = getPreferredSelectedItem(allStructures)
+            reload(parent)
+        }.apply { show() }
+    }
+
     private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
         val i = Intent(activityContext, klazz)
         putIntentExtras(i, si)
@@ -433,7 +462,10 @@
         val currentApps = panelsAndStructures.map { it.componentName }.toSet()
         val allApps = controlsListingController.get()
                 .getCurrentServices().map { it.componentName }.toSet()
-        createMenu(extraApps = (allApps - currentApps).isNotEmpty())
+        createMenu(
+                selectionItem = selectionItem,
+                extraApps = (allApps - currentApps).isNotEmpty(),
+        )
     }
 
     private fun createPanelView(componentName: ComponentName) {
@@ -472,7 +504,7 @@
         }
     }
 
-    private fun createMenu(extraApps: Boolean) {
+    private fun createMenu(selectionItem: SelectionItem, extraApps: Boolean) {
         val isPanel = selectedItem is SelectedItem.PanelItem
         val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
                 ?: EMPTY_STRUCTURE
@@ -490,6 +522,13 @@
                             ADD_APP_ID
                     ))
                 }
+                if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) &&
+                        controlsController.get().canRemoveFavorites(selectedItem.componentName)) {
+                    add(OverflowMenuAdapter.MenuItem(
+                            context.getText(R.string.controls_menu_remove),
+                            REMOVE_APP_ID,
+                    ))
+                }
             } else {
                 add(OverflowMenuAdapter.MenuItem(
                         context.getText(R.string.controls_menu_add),
@@ -529,6 +568,9 @@
                                 ADD_APP_ID -> startProviderSelectorActivity()
                                 ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
                                 EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
+                                REMOVE_APP_ID -> startRemovingApp(
+                                        selectedStructure.componentName, selectionItem.appName
+                                )
                             }
                             dismiss()
                         }
@@ -546,8 +588,12 @@
             RenderInfo.registerComponentIcon(it.componentName, it.icon)
         }
 
-        var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
-            addAll(items)
+        val adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
+            add(selected)
+            addAll(items
+                    .filter { it !== selected }
+                    .sortedBy { it.appName.toString() }
+            )
         }
 
         val iconSize = context.resources
@@ -728,6 +774,7 @@
             it.value.dismiss()
         }
         controlActionCoordinator.closeDialogs()
+        removeAppDialog?.cancel()
     }
 
     override fun hide(parent: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 3b6ab20..78e87ca 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -71,7 +71,7 @@
                 taskView.post {
                     val roundedCorner =
                         activityContext.resources.getDimensionPixelSize(
-                            R.dimen.notification_corner_radius
+                            R.dimen.controls_panel_corner_radius
                         )
                     val radii = FloatArray(8) { roundedCorner.toFloat() }
                     taskView.background =
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index e39073b..ff1f312 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -28,6 +28,8 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.condition.ConditionalCoreStartable;
@@ -68,6 +70,7 @@
         private final DreamSmartspaceController mSmartSpaceController;
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final SmartSpaceComplication mComplication;
+        private final FeatureFlags mFeatureFlags;
 
         private final BcSmartspaceDataPlugin.SmartspaceTargetListener mSmartspaceListener =
                 new BcSmartspaceDataPlugin.SmartspaceTargetListener() {
@@ -85,15 +88,21 @@
                 DreamOverlayStateController dreamOverlayStateController,
                 SmartSpaceComplication smartSpaceComplication,
                 DreamSmartspaceController smartSpaceController,
-                @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+                @Named(DREAM_PRETEXT_MONITOR) Monitor monitor,
+                FeatureFlags featureFlags) {
             super(monitor);
             mDreamOverlayStateController = dreamOverlayStateController;
             mComplication = smartSpaceComplication;
             mSmartSpaceController = smartSpaceController;
+            mFeatureFlags = featureFlags;
         }
 
         @Override
         public void onStart() {
+            if (mFeatureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)) {
+                return;
+            }
+
             mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
                 @Override
                 public void onStateChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b2609a7..4fb763f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -231,6 +231,10 @@
     val SMARTSPACE_DATE_WEATHER_DECOUPLED =
         sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = false)
 
+    // TODO(b/270223352): Tracking Bug
+    @JvmField
+    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay")
+
     // 500 - quick settings
 
     val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
@@ -373,6 +377,9 @@
     // TODO(b/267166152) : Tracking Bug
     val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations")
 
+    // TODO(b/270437894): Tracking Bug
+    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index f964cb3..8ae171f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -950,9 +950,9 @@
             return false
         }
 
-        // We don't do the shared element on tablets because they're large and the smartspace has to
-        // fly across large distances, which is distracting.
-        if (Utilities.isTablet(context)) {
+        // We don't do the shared element on large screens because the smartspace has to fly across
+        // large distances, which is distracting.
+        if (Utilities.isLargeScreen(context)) {
             return false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index baadc66..84abf57 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -24,8 +24,10 @@
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
 import android.os.Looper
 import android.os.UserHandle
+import android.util.Log
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.Dumpable
+import com.android.systemui.R
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -35,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.DevicePosture
 import com.android.systemui.user.data.repository.UserRepository
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -47,8 +50,10 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformLatest
@@ -82,6 +87,12 @@
 
     /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
     val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
+
+    /**
+     * Whether face authentication is supported for the current device posture. Face auth can be
+     * restricted to specific postures using [R.integer.config_face_auth_supported_posture]
+     */
+    val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
 }
 
 @SysUISingleton
@@ -98,11 +109,27 @@
     @Background backgroundDispatcher: CoroutineDispatcher,
     biometricManager: BiometricManager?,
     @Main looper: Looper,
+    devicePostureRepository: DevicePostureRepository,
     dumpManager: DumpManager,
 ) : BiometricSettingsRepository, Dumpable {
 
+    override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
+
     init {
         dumpManager.registerDumpable(this)
+        val configFaceAuthSupportedPosture =
+            DevicePosture.toPosture(
+                context.resources.getInteger(R.integer.config_face_auth_supported_posture)
+            )
+        isFaceAuthSupportedInCurrentPosture =
+            if (configFaceAuthSupportedPosture == DevicePosture.UNKNOWN) {
+                    flowOf(true)
+                } else {
+                    devicePostureRepository.currentDevicePosture.map {
+                        it == configFaceAuthSupportedPosture
+                    }
+                }
+                .onEach { Log.d(TAG, "isFaceAuthSupportedInCurrentPosture value changed to: $it") }
     }
 
     override fun dump(pw: PrintWriter, args: Array<String?>) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
new file mode 100644
index 0000000..adb1e01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.statusbar.policy.DevicePostureController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Provide current device posture state. */
+interface DevicePostureRepository {
+    /** Provides the current device posture. */
+    val currentDevicePosture: Flow<DevicePosture>
+}
+
+@SysUISingleton
+class DevicePostureRepositoryImpl
+@Inject
+constructor(private val postureController: DevicePostureController) : DevicePostureRepository {
+    override val currentDevicePosture: Flow<DevicePosture>
+        get() = conflatedCallbackFlow {
+            val sendPostureUpdate = { posture: Int ->
+                val currentDevicePosture = DevicePosture.toPosture(posture)
+                trySendWithFailureLogging(
+                    currentDevicePosture,
+                    TAG,
+                    "Error sending posture update to $currentDevicePosture"
+                )
+            }
+            val callback = DevicePostureController.Callback { sendPostureUpdate(it) }
+            postureController.addCallback(callback)
+            sendPostureUpdate(postureController.devicePosture)
+
+            awaitClose { postureController.removeCallback(callback) }
+        }
+
+    companion object {
+        const val TAG = "PostureRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 4a262f5..f27f899 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -31,6 +31,8 @@
     @Binds
     fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
 
+    @Binds fun devicePostureRepository(impl: DevicePostureRepositoryImpl): DevicePostureRepository
+
     @Binds
     fun biometricSettingsRepository(
         impl: BiometricSettingsRepositoryImpl
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt
new file mode 100644
index 0000000..fff7cfe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard.shared.model
+
+import com.android.systemui.statusbar.policy.DevicePostureController
+
+/** Represents the possible posture states of the device. */
+enum class DevicePosture {
+    UNKNOWN,
+    CLOSED,
+    HALF_OPENED,
+    OPENED,
+    FLIPPED;
+
+    companion object {
+        fun toPosture(@DevicePostureController.DevicePostureInt posture: Int): DevicePosture {
+            return when (posture) {
+                DevicePostureController.DEVICE_POSTURE_CLOSED -> CLOSED
+                DevicePostureController.DEVICE_POSTURE_HALF_OPENED -> HALF_OPENED
+                DevicePostureController.DEVICE_POSTURE_OPENED -> OPENED
+                DevicePostureController.DEVICE_POSTURE_FLIPPED -> FLIPPED
+                DevicePostureController.DEVICE_POSTURE_UNKNOWN -> UNKNOWN
+                else -> UNKNOWN
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 1e3b60c..e7184f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -187,6 +187,7 @@
                             previewMode.isInPreviewMode &&
                                 previewMode.shouldHighlightSelectedAffordance &&
                                 !isSelected,
+                        forceInactive = previewMode.isInPreviewMode
                     )
                 }
                 .distinctUntilChanged()
@@ -198,6 +199,7 @@
         isClickable: Boolean,
         isSelected: Boolean,
         isDimmed: Boolean,
+        forceInactive: Boolean,
     ): KeyguardQuickAffordanceViewModel {
         return when (this) {
             is KeyguardQuickAffordanceModel.Visible ->
@@ -213,7 +215,7 @@
                         )
                     },
                     isClickable = isClickable,
-                    isActivated = activationState is ActivationState.Active,
+                    isActivated = !forceInactive && activationState is ActivationState.Active,
                     isSelected = isSelected,
                     useLongPress = quickAffordanceInteractor.useLongPress,
                     isDimmed = isDimmed,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 817de79..642c9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -62,6 +62,15 @@
         return factory.create("NotifLog", maxSize, false /* systrace */);
     }
 
+    /** Provides a logging buffer for all logs related to notifications on the lockscreen. */
+    @Provides
+    @SysUISingleton
+    @NotificationLockscreenLog
+    public static LogBuffer provideNotificationLockScreenLogBuffer(
+            LogBufferFactory factory) {
+        return factory.create("NotifLockscreenLog", 50, false /* systrace */);
+    }
+
     /** Provides a logging buffer for logs related to heads up presentation of notifications. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
new file mode 100644
index 0000000..a2d381e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for notification & lockscreen related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationLockscreenLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index eb6893f..e70a2f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1339,10 +1339,13 @@
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key) ?: return
-
+        val isEligibleForResume =
+            removed.isLocalSession() ||
+                (mediaFlags.isRemoteResumeAllowed() &&
+                    removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
         if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
-        } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
+        } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) {
             convertToResumePlayer(key, removed)
         } else if (mediaFlags.isRetainingPlayersEnabled()) {
             handlePossibleRemoval(key, removed, notificationRemoved = true)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 2d10b82..2af21c4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
@@ -63,7 +64,8 @@
     private val tunerService: TunerService,
     private val mediaBrowserFactory: ResumeMediaBrowserFactory,
     dumpManager: DumpManager,
-    private val systemClock: SystemClock
+    private val systemClock: SystemClock,
+    private val mediaFlags: MediaFlags,
 ) : MediaDataManager.Listener, Dumpable {
 
     private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
@@ -231,7 +233,11 @@
                 mediaBrowser = null
             }
             // If we don't have a resume action, check if we haven't already
-            if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession()) {
+            val isEligibleForResume =
+                data.isLocalSession() ||
+                    (mediaFlags.isRemoteResumeAllowed() &&
+                        data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
+            if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
                 Log.d(TAG, "Checking for service component for " + data.packageName)
                 val pm = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index c3fa76e..9bc66f6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -61,4 +61,7 @@
 
     /** If true, do not automatically dismiss the recommendation card */
     fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
+
+    /** Check whether we allow remote media to generate resume controls */
+    fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index d4991f9..9b9d561 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
 
 /**
  * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
@@ -150,9 +150,9 @@
         val displayWidthPx = windowMetrics.bounds.width()
         val displayHeightPx = windowMetrics.bounds.height()
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
-        val isTablet = isTablet(context)
+        val isLargeScreen = isLargeScreen(context)
         val taskbarSize =
-            if (isTablet) {
+            if (isLargeScreen) {
                 resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
             } else {
                 0
@@ -166,7 +166,7 @@
             displayWidthPx,
             displayHeightPx,
             taskbarSize,
-            isTablet,
+            isLargeScreen,
             currentRotation,
             isRtl
         )
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 88d5eaa..1c90154 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -23,7 +23,7 @@
 import com.android.internal.R as AndroidR
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
-import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -61,8 +61,8 @@
         val width = windowMetrics.bounds.width()
         var height = maximumWindowHeight
 
-        val isTablet = isTablet(context)
-        if (isTablet) {
+        val isLargeScreen = isLargeScreen(context)
+        if (isLargeScreen) {
             val taskbarSize =
                 context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
             height -= taskbarSize
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 97c290d..f817439 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -37,7 +37,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -1724,7 +1724,7 @@
 
     private void setNavigationIconHints(int hints) {
         if (hints == mNavigationIconHints) return;
-        if (!isTablet(mContext)) {
+        if (!isLargeScreen(mContext)) {
             // All IME functions handled by launcher via Sysui flags for large screen
             final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
             final boolean oldBackAlt =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 8c19111..153d5a6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -21,7 +21,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 
 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
-import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -91,7 +91,7 @@
     private final DisplayManager mDisplayManager;
     private final TaskbarDelegate mTaskbarDelegate;
     private int mNavMode;
-    @VisibleForTesting boolean mIsTablet;
+    @VisibleForTesting boolean mIsLargeScreen;
 
     /** A displayId - nav bar maps. */
     @VisibleForTesting
@@ -138,16 +138,16 @@
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
                 dumpManager, autoHideController, lightBarController, pipOptional,
                 backAnimation.orElse(null), taskStackChangeListeners);
-        mIsTablet = isTablet(mContext);
+        mIsLargeScreen = isLargeScreen(mContext);
         dumpManager.registerDumpable(this);
     }
 
     @Override
     public void onConfigChanged(Configuration newConfig) {
-        boolean isOldConfigTablet = mIsTablet;
-        mIsTablet = isTablet(mContext);
+        boolean isOldConfigLargeScreen = mIsLargeScreen;
+        mIsLargeScreen = isLargeScreen(mContext);
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
-        boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+        boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
         Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
@@ -235,8 +235,9 @@
 
     /** @return {@code true} if taskbar is enabled, false otherwise */
     private boolean initializeTaskbarIfNecessary() {
-        // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet
-        boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW);
+        // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen
+        boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled(
+                Flags.HIDE_NAVBAR_WINDOW);
 
         if (taskbarEnabled) {
             Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
@@ -258,7 +259,7 @@
     @Override
     public void onDisplayReady(int displayId) {
         Display display = mDisplayManager.getDisplay(displayId);
-        mIsTablet = isTablet(mContext);
+        mIsLargeScreen = isLargeScreen(mContext);
         createNavigationBar(display, null /* savedState */, null /* result */);
     }
 
@@ -470,7 +471,7 @@
 
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mIsTablet=" + mIsTablet);
+        pw.println("mIsLargeScreen=" + mIsLargeScreen);
         pw.println("mNavMode=" + mNavMode);
         for (int i = 0; i < mNavigationBars.size(); i++) {
             if (i > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 645b125..346acf9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.recents;
 
-import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
@@ -265,7 +265,7 @@
                     .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
             View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
             if (!QuickStepContract.isGesturalMode(mNavBarMode)
-            	    && hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) {
+            	    && hasSoftNavigationBar(mContext.getDisplayId()) && !isLargeScreen(mContext)) {
                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
                 swapChildrenIfRtlAndVertical(buttons);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 287e810..33a3125 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 
 /**
@@ -67,14 +68,25 @@
     interface Callback {
 
         /**
+         * Same as {@link onUserChanging(Int, Context, CountDownLatch)} but the latch will be
+         * auto-decremented after the completion of this method.
+         */
+        @JvmDefault
+        fun onUserChanging(newUser: Int, userContext: Context) {}
+
+        /**
          * Notifies that the current user is being changed.
          * Override this method to run things while the screen is frozen for the user switch.
          * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the
          * screen further. Please be aware that code executed in this callback will lengthen the
-         * user switch duration.
+         * user switch duration. When overriding this method, countDown() MUST be called on the
+         * latch once execution is complete.
          */
         @JvmDefault
-        fun onUserChanging(newUser: Int, userContext: Context) {}
+        fun onUserChanging(newUser: Int, userContext: Context, latch: CountDownLatch) {
+            onUserChanging(newUser, userContext)
+            latch.countDown()
+        }
 
         /**
          * Notifies that the current user has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 9f551c6..8674036 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -183,9 +183,22 @@
         Log.i(TAG, "Switching to user $newUserId")
 
         setUserIdInternal(newUserId)
-        notifySubscribers {
-            onUserChanging(newUserId, userContext)
-        }.await()
+
+        val list = synchronized(callbacks) {
+            callbacks.toList()
+        }
+        val latch = CountDownLatch(list.size)
+        list.forEach {
+            val callback = it.callback.get()
+            if (callback != null) {
+                it.executor.execute {
+                    callback.onUserChanging(userId, userContext, latch)
+                }
+            } else {
+                latch.countDown()
+            }
+        }
+        latch.await()
     }
 
     @WorkerThread
@@ -225,25 +238,18 @@
         }
     }
 
-    private inline fun notifySubscribers(
-            crossinline action: UserTracker.Callback.() -> Unit
-    ): CountDownLatch {
+    private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) {
         val list = synchronized(callbacks) {
             callbacks.toList()
         }
-        val latch = CountDownLatch(list.size)
 
         list.forEach {
             if (it.callback.get() != null) {
                 it.executor.execute {
                     it.callback.get()?.action()
-                    latch.countDown()
                 }
-            } else {
-                latch.countDown()
             }
         }
-        return latch
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 0a5e986..11582d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -29,7 +29,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.SynchronousUserSwitchObserver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -52,7 +51,9 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.NotificationChannels;
@@ -73,6 +74,8 @@
 
     private final Context mContext;
     private final Handler mHandler = new Handler();
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
     private final Executor mUiBgExecutor;
     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
     private final CommandQueue mCommandQueue;
@@ -82,10 +85,14 @@
     public InstantAppNotifier(
             Context context,
             CommandQueue commandQueue,
+            UserTracker userTracker,
+            @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             KeyguardStateController keyguardStateController) {
         mContext = context;
         mCommandQueue = commandQueue;
+        mUserTracker = userTracker;
+        mMainExecutor = mainExecutor;
         mUiBgExecutor = uiBgExecutor;
         mKeyguardStateController = keyguardStateController;
     }
@@ -93,11 +100,7 @@
     @Override
     public void start() {
         // listen for user / profile change.
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);
-        } catch (RemoteException e) {
-            // Ignore
-        }
+        mUserTracker.addCallback(mUserSwitchListener, mMainExecutor);
 
         mCommandQueue.addCallback(this);
         mKeyguardStateController.addCallback(this);
@@ -129,13 +132,10 @@
         updateForegroundInstantApps();
     }
 
-    private final SynchronousUserSwitchObserver mUserSwitchListener =
-            new SynchronousUserSwitchObserver() {
+    private final UserTracker.Callback mUserSwitchListener =
+            new UserTracker.Callback() {
                 @Override
-                public void onUserSwitching(int newUserId) throws RemoteException {}
-
-                @Override
-                public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                public void onUserChanged(int newUser, Context userContext) {
                     mHandler.post(
                             () -> {
                                 updateForegroundInstantApps();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index 4464531..88d9ffc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -13,7 +13,7 @@
 
 package com.android.systemui.statusbar.notification
 
-import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.log.dagger.NotificationLockscreenLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.statusbar.StatusBarState
@@ -21,7 +21,12 @@
 
 class NotificationWakeUpCoordinatorLogger
 @Inject
-constructor(@NotificationLog private val buffer: LogBuffer) {
+constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
+    private var lastSetDozeAmountLogWasFractional = false
+    private var lastSetDozeAmountLogState = -1
+    private var lastSetDozeAmountLogSource = "undefined"
+    private var lastOnDozeAmountChangedLogWasFractional = false
+
     fun logSetDozeAmount(
         linear: Float,
         eased: Float,
@@ -29,6 +34,20 @@
         state: Int,
         changed: Boolean,
     ) {
+        // Avoid logging on every frame of the animation if important values are not changing
+        val isFractional = linear != 1f && linear != 0f
+        if (
+            lastSetDozeAmountLogWasFractional &&
+                isFractional &&
+                lastSetDozeAmountLogState == state &&
+                lastSetDozeAmountLogSource == source
+        ) {
+            return
+        }
+        lastSetDozeAmountLogWasFractional = isFractional
+        lastSetDozeAmountLogState = state
+        lastSetDozeAmountLogSource = source
+
         buffer.log(
             TAG,
             DEBUG,
@@ -66,6 +85,10 @@
     }
 
     fun logOnDozeAmountChanged(linear: Float, eased: Float) {
+        // Avoid logging on every frame of the animation when values are fractional
+        val isFractional = linear != 1f && linear != 0f
+        if (lastOnDozeAmountChangedLogWasFractional && isFractional) return
+        lastOnDozeAmountChangedLogWasFractional = isFractional
         buffer.log(
             TAG,
             DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 6c532a5..e6b76ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -22,8 +22,6 @@
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
-import android.app.IActivityManager;
-import android.app.SynchronousUserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -134,7 +132,6 @@
     private final NextAlarmController mNextAlarmController;
     private final AlarmManager mAlarmManager;
     private final UserInfoController mUserInfoController;
-    private final IActivityManager mIActivityManager;
     private final UserManager mUserManager;
     private final UserTracker mUserTracker;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -149,6 +146,7 @@
     private final KeyguardStateController mKeyguardStateController;
     private final LocationController mLocationController;
     private final PrivacyItemController mPrivacyItemController;
+    private final Executor mMainExecutor;
     private final Executor mUiBgExecutor;
     private final SensorPrivacyController mSensorPrivacyController;
     private final RecordingController mRecordingController;
@@ -168,16 +166,17 @@
     @Inject
     public PhoneStatusBarPolicy(StatusBarIconController iconController,
             CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher,
-            @UiBackground Executor uiBgExecutor, @Main Looper looper, @Main Resources resources,
-            CastController castController, HotspotController hotspotController,
-            BluetoothController bluetoothController, NextAlarmController nextAlarmController,
-            UserInfoController userInfoController, RotationLockController rotationLockController,
-            DataSaverController dataSaverController, ZenModeController zenModeController,
+            @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, @Main Looper looper,
+            @Main Resources resources, CastController castController,
+            HotspotController hotspotController, BluetoothController bluetoothController,
+            NextAlarmController nextAlarmController, UserInfoController userInfoController,
+            RotationLockController rotationLockController, DataSaverController dataSaverController,
+            ZenModeController zenModeController,
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
             LocationController locationController,
-            SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
-            AlarmManager alarmManager, UserManager userManager, UserTracker userTracker,
+            SensorPrivacyController sensorPrivacyController, AlarmManager alarmManager,
+            UserManager userManager, UserTracker userTracker,
             DevicePolicyManager devicePolicyManager, RecordingController recordingController,
             @Nullable TelecomManager telecomManager, @DisplayId int displayId,
             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
@@ -195,7 +194,6 @@
         mNextAlarmController = nextAlarmController;
         mAlarmManager = alarmManager;
         mUserInfoController = userInfoController;
-        mIActivityManager = iActivityManager;
         mUserManager = userManager;
         mUserTracker = userTracker;
         mDevicePolicyManager = devicePolicyManager;
@@ -208,6 +206,7 @@
         mPrivacyItemController = privacyItemController;
         mSensorPrivacyController = sensorPrivacyController;
         mRecordingController = recordingController;
+        mMainExecutor = mainExecutor;
         mUiBgExecutor = uiBgExecutor;
         mTelecomManager = telecomManager;
         mRingerModeTracker = ringerModeTracker;
@@ -256,11 +255,7 @@
         mRingerModeTracker.getRingerModeInternal().observeForever(observer);
 
         // listen for user / profile change.
-        try {
-            mIActivityManager.registerUserSwitchObserver(mUserSwitchListener, TAG);
-        } catch (RemoteException e) {
-            // Ignore
-        }
+        mUserTracker.addCallback(mUserSwitchListener, mMainExecutor);
 
         // TTY status
         updateTTY();
@@ -555,15 +550,15 @@
         });
     }
 
-    private final SynchronousUserSwitchObserver mUserSwitchListener =
-            new SynchronousUserSwitchObserver() {
+    private final UserTracker.Callback mUserSwitchListener =
+            new UserTracker.Callback() {
                 @Override
-                public void onUserSwitching(int newUserId) throws RemoteException {
+                public void onUserChanging(int newUser, Context userContext) {
                     mHandler.post(() -> mUserInfoController.reloadUserInfo());
                 }
 
                 @Override
-                public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                public void onUserChanged(int newUser, Context userContext) {
                     mHandler.post(() -> {
                         updateAlarm();
                         updateManagedProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt
index 2683971..981f429 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt
@@ -61,8 +61,6 @@
         foldStateProvider.stop()
     }
 
-    override fun onHingeAngleUpdate(angle: Float) {}
-
     override fun onFoldUpdate(@FoldUpdate update: Int) {
         val now = clock.elapsedRealtime()
         when (update) {
@@ -77,6 +75,10 @@
         }
     }
 
+    override fun onUnfoldedScreenAvailable() {
+        Log.d(TAG, "Unfolded screen available")
+    }
+
     private fun dispatchState(@LoggedFoldedStates current: Int) {
         val now = clock.elapsedRealtime()
         val previous = lastState
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ad1e5fe..b2b7c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,11 +17,8 @@
 
 package com.android.systemui.user.data.repository
 
-import android.app.IActivityManager
-import android.app.UserSwitchObserver
 import android.content.Context
 import android.content.pm.UserInfo
-import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
@@ -118,7 +115,6 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
-    private val activityManager: IActivityManager,
     featureFlags: FeatureFlags,
 ) : UserRepository {
 
@@ -203,18 +199,18 @@
     private fun observeUserSwitching() {
         conflatedCallbackFlow {
                 val callback =
-                    object : UserSwitchObserver() {
-                        override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
+                    object : UserTracker.Callback {
+                        override fun onUserChanging(newUser: Int, userContext: Context) {
                             trySendWithFailureLogging(true, TAG, "userSwitching started")
                         }
 
-                        override fun onUserSwitchComplete(newUserId: Int) {
+                        override fun onUserChanged(newUserId: Int, userContext: Context) {
                             trySendWithFailureLogging(false, TAG, "userSwitching completed")
                         }
                     }
-                activityManager.registerUserSwitchObserver(callback, TAG)
+                tracker.addCallback(callback, mainDispatcher.asExecutor())
                 trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
-                awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
+                awaitClose { tracker.removeCallback(callback) }
             }
             .onEach { _isUserSwitchingInProgress.value = it }
             // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d912793..ed928702 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -18,8 +18,10 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
 import android.view.inputmethod.InputMethodManager
 import android.widget.EditText
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -30,6 +32,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
@@ -37,6 +40,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -76,7 +80,9 @@
     Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
         .thenReturn(passwordEntry)
     `when`(keyguardPasswordView.resources).thenReturn(context.resources)
-    keyguardPasswordViewController =
+    `when`(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
+        .thenReturn(mock(ImageView::class.java))
+     keyguardPasswordViewController =
         KeyguardPasswordViewController(
             keyguardPasswordView,
             keyguardUpdateMonitor,
@@ -113,6 +119,18 @@
   }
 
   @Test
+  fun onApplyWindowInsetsListener_onApplyWindowInsets() {
+      `when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+      val argumentCaptor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+
+      keyguardPasswordViewController.onViewAttached()
+      verify(keyguardPasswordView).setOnApplyWindowInsetsListener(argumentCaptor.capture())
+      argumentCaptor.value.onApplyWindowInsets(keyguardPasswordView, null)
+
+      verify(keyguardPasswordView).hideKeyboard()
+  }
+
+  @Test
   fun testHideKeyboardWhenOnPause() {
     keyguardPasswordViewController.onPause()
     keyguardPasswordView.post {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fe812dbf..3c80dad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -56,8 +56,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.app.trust.TrustManager;
@@ -90,7 +88,6 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.IRemoteCallback;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -148,6 +145,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -231,8 +229,6 @@
     @Mock
     private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
     @Mock
-    private IActivityManager mActivityService;
-    @Mock
     private SessionTracker mSessionTracker;
     @Mock
     private UiEventLogger mUiEventLogger;
@@ -269,8 +265,6 @@
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
-        when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
         when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
         when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
@@ -310,13 +304,11 @@
 
         mMockitoSession = ExtendedMockito.mockitoSession()
                 .spyStatic(SubscriptionManager.class)
-                .spyStatic(ActivityManager.class)
                 .startMocking();
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
         KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
         when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
-        ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.systemui.R.integer.config_face_auth_supported_posture,
@@ -1071,11 +1063,6 @@
 
     @Test
     public void testBiometricsCleared_whenUserSwitches() throws Exception {
-        final IRemoteCallback reply = new IRemoteCallback.Stub() {
-            @Override
-            public void sendResult(Bundle data) {
-            } // do nothing
-        };
         final BiometricAuthenticated dummyAuthentication =
                 new BiometricAuthenticated(true /* authenticated */, true /* strong */);
         mKeyguardUpdateMonitor.mUserFaceAuthenticated.put(0 /* user */, dummyAuthentication);
@@ -1083,18 +1070,13 @@
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1);
         assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1);
 
-        mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, reply);
+        mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, new CountDownLatch(0));
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0);
         assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0);
     }
 
     @Test
     public void testMultiUserJankMonitor_whenUserSwitches() throws Exception {
-        final IRemoteCallback reply = new IRemoteCallback.Stub() {
-            @Override
-            public void sendResult(Bundle data) {
-            } // do nothing
-        };
         mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */);
         verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH);
         verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index e35b2a3..28e80057 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -39,11 +39,9 @@
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import java.io.File
-import java.util.Optional
-import java.util.function.Consumer
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -58,7 +56,9 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -66,9 +66,10 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.clearInvocations
 import org.mockito.MockitoAnnotations
+import java.io.File
+import java.util.*
+import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -146,6 +147,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
         `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
 
         delayableExecutor = FakeExecutor(FakeSystemClock())
@@ -945,6 +947,28 @@
         controller.bindComponentForPanel(TEST_COMPONENT)
         verify(bindingController).bindServiceForPanel(TEST_COMPONENT)
     }
+
+    @Test
+    fun testRemoveFavoriteRemovesFavorite() {
+        val componentName = ComponentName(context, "test.Cls")
+        controller.addFavorite(
+                componentName,
+                "test structure",
+                ControlInfo(
+                        controlId = "testId",
+                        controlTitle = "Test Control",
+                        controlSubtitle = "test control subtitle",
+                        deviceType = DeviceTypes.TYPE_LIGHT,
+                ),
+        )
+
+        controller.removeFavorites(componentName)
+        delayableExecutor.runAllReady()
+
+        verify(authorizedPanelsRepository)
+                .removeAuthorizedPanels(eq(setOf(componentName.packageName)))
+        assertThat(controller.getFavorites()).isEmpty()
+    }
 }
 
 private class DidRunRunnable() : Runnable {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index b91a3fd..7ac1953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -115,6 +115,18 @@
         assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
     }
 
+    @Test
+    fun testRemoveAuthorizedPackageRemovesIt() {
+        val sharedPrefs = FakeSharedPreferences()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+        val repository = createRepository(fileManager)
+        repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+
+        repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+
+        assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
+    }
+
     private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
         return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
new file mode 100644
index 0000000..1e8cd41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.FakeSystemUIDialogController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsDialogsFactoryTest : SysuiTestCase() {
+
+    private companion object {
+        const val APP_NAME = "Test App"
+    }
+
+    private val fakeDialogController = FakeSystemUIDialogController()
+
+    private lateinit var underTest: ControlsDialogsFactory
+
+    @Before
+    fun setup() {
+        underTest = ControlsDialogsFactory { fakeDialogController.dialog }
+    }
+
+    @Test
+    fun testCreatesRemoveAppDialog() {
+        val dialog = underTest.createRemoveAppDialog(context, APP_NAME) {}
+
+        verify(dialog)
+            .setTitle(
+                eq(context.getString(R.string.controls_panel_remove_app_authorization, APP_NAME))
+            )
+        verify(dialog).setCanceledOnTouchOutside(eq(true))
+    }
+
+    @Test
+    fun testPositiveClickRemoveAppDialogWorks() {
+        var dialogResult: Boolean? = null
+        underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
+
+        fakeDialogController.clickPositive()
+
+        assertThat(dialogResult).isTrue()
+    }
+
+    @Test
+    fun testNeutralClickRemoveAppDialogWorks() {
+        var dialogResult: Boolean? = null
+        underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
+
+        fakeDialogController.clickNeutral()
+
+        assertThat(dialogResult).isFalse()
+    }
+
+    @Test
+    fun testCancelRemoveAppDialogWorks() {
+        var dialogResult: Boolean? = null
+        underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
+
+        fakeDialogController.cancel()
+
+        assertThat(dialogResult).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index aa90e2a..23faa99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.FakeSystemUIDialogController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -63,21 +64,20 @@
 import com.android.wm.shell.TaskView
 import com.android.wm.shell.TaskViewFactory
 import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-import java.util.Optional
-import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import java.util.Optional
+import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -98,13 +98,15 @@
     @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
     @Mock lateinit var featureFlags: FeatureFlags
     @Mock lateinit var packageManager: PackageManager
-    val sharedPreferences = FakeSharedPreferences()
-    lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
-    var uiExecutor = FakeExecutor(FakeSystemClock())
-    var bgExecutor = FakeExecutor(FakeSystemClock())
-    lateinit var underTest: ControlsUiControllerImpl
-    lateinit var parent: FrameLayout
+    private val sharedPreferences = FakeSharedPreferences()
+    private val fakeDialogController = FakeSystemUIDialogController()
+    private val uiExecutor = FakeExecutor(FakeSystemClock())
+    private val bgExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+    private lateinit var parent: FrameLayout
+    private lateinit var underTest: ControlsUiControllerImpl
 
     @Before
     fun setup() {
@@ -125,12 +127,12 @@
 
         underTest =
             ControlsUiControllerImpl(
-                Lazy { controlsController },
+                { controlsController },
                 context,
                 packageManager,
                 uiExecutor,
                 bgExecutor,
-                Lazy { controlsListingController },
+                { controlsListingController },
                 controlActionCoordinator,
                 activityStarter,
                 iconCache,
@@ -142,7 +144,8 @@
                 controlsSettingsRepository,
                 authorizedPanelsRepository,
                 featureFlags,
-                dumpManager
+                ControlsDialogsFactory { fakeDialogController.dialog },
+                dumpManager,
             )
         `when`(
                 userFileManager.getSharedPreferences(
@@ -410,8 +413,45 @@
         verify(controlsListingController, never()).removeCallback(any())
     }
 
+    @Test
+    fun testRemovingAppsRemovesFavorite() {
+        val componentName = ComponentName(context, "cls")
+        whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
+        val panel = SelectedItem.PanelItem("App name", componentName)
+        sharedPreferences
+            .edit()
+            .putString("controls_component", panel.componentName.flattenToString())
+            .putString("controls_structure", panel.appName.toString())
+            .putBoolean("controls_is_panel", true)
+            .commit()
+        underTest.show(parent, {}, context)
+        underTest.startRemovingApp(componentName, "Test App")
+
+        fakeDialogController.clickPositive()
+
+        verify(controlsController).removeFavorites(eq(componentName))
+        assertThat(underTest.getPreferredSelectedItem(emptyList()))
+            .isEqualTo(SelectedItem.EMPTY_SELECTION)
+        with(sharedPreferences) {
+            assertThat(contains("controls_component")).isFalse()
+            assertThat(contains("controls_structure")).isFalse()
+            assertThat(contains("controls_is_panel")).isFalse()
+        }
+    }
+
+    @Test
+    fun testHideCancelsTheRemoveAppDialog() {
+        val componentName = ComponentName(context, "cls")
+        underTest.show(parent, {}, context)
+        underTest.startRemovingApp(componentName, "Test App")
+
+        underTest.hide(parent)
+
+        verify(fakeDialogController.dialog).cancel()
+    }
+
     private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
-        val activity = ComponentName("pkg", "activity")
+        val activity = ComponentName(context, "activity")
         sharedPreferences
             .edit()
             .putString("controls_component", panel.componentName.flattenToString())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index ef62abf..175da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -33,6 +33,8 @@
 import com.android.systemui.condition.SelfExecutingMonitor;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.shared.condition.Condition;
 import com.android.systemui.shared.condition.Monitor;
@@ -65,6 +67,9 @@
     @Mock
     private View mBcSmartspaceView;
 
+    @Mock
+    private FeatureFlags mFeatureFlags;
+
     private Monitor mMonitor;
 
     private final Set<Condition> mPreconditions = new HashSet<>();
@@ -73,6 +78,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mMonitor = SelfExecutingMonitor.createInstance();
+
+        when(mFeatureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)).thenReturn(false);
     }
 
     /**
@@ -85,12 +92,22 @@
         verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
     }
 
-    private SmartSpaceComplication.Registrant getRegistrant() {
-        return new SmartSpaceComplication.Registrant(
-                mDreamOverlayStateController,
-                mComplication,
-                mSmartspaceController,
-                mMonitor);
+    @Test
+    public void testRegistrantStart_featureEnabled_addOverlayStateCallback() {
+        final SmartSpaceComplication.Registrant registrant = getRegistrant();
+        registrant.start();
+
+        verify(mDreamOverlayStateController).addCallback(any());
+    }
+
+    @Test
+    public void testRegistrantStart_featureDisabled_doesNotAddOverlayStateCallback() {
+        when(mFeatureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)).thenReturn(true);
+
+        final SmartSpaceComplication.Registrant registrant = getRegistrant();
+        registrant.start();
+
+        verify(mDreamOverlayStateController, never()).addCallback(any());
     }
 
     @Test
@@ -188,4 +205,13 @@
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView);
         assertEquals(viewHolder.getView(), viewHolder.getView());
     }
+
+    private SmartSpaceComplication.Registrant getRegistrant() {
+        return new SmartSpaceComplication.Registrant(
+                mDreamOverlayStateController,
+                mComplication,
+                mSmartspaceController,
+                mMonitor,
+                mFeatureFlags);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 21ad5e2..5dc04f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -30,6 +30,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
@@ -38,11 +39,14 @@
 import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPRINT
 import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT
 import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
+import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -62,6 +66,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
@@ -78,6 +83,7 @@
     private lateinit var biometricManagerCallback:
         ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
     private lateinit var userRepository: FakeUserRepository
+    private lateinit var devicePostureRepository: FakeDevicePostureRepository
 
     private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
@@ -90,6 +96,7 @@
         testDispatcher = StandardTestDispatcher()
         testScope = TestScope(testDispatcher)
         userRepository = FakeUserRepository()
+        devicePostureRepository = FakeDevicePostureRepository()
     }
 
     private suspend fun createBiometricSettingsRepository() {
@@ -108,6 +115,7 @@
                 looper = testableLooper!!.looper,
                 dumpManager = dumpManager,
                 biometricManager = biometricManager,
+                devicePostureRepository = devicePostureRepository,
             )
         testScope.runCurrent()
     }
@@ -299,6 +307,50 @@
             verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
         }
 
+    @Test
+    fun faceAuthIsAlwaysSupportedIfSpecificPostureIsNotConfigured() =
+        testScope.runTest {
+            overrideResource(
+                R.integer.config_face_auth_supported_posture,
+                DevicePostureController.DEVICE_POSTURE_UNKNOWN
+            )
+
+            createBiometricSettingsRepository()
+
+            assertThat(collectLastValue(underTest.isFaceAuthSupportedInCurrentPosture)()).isTrue()
+        }
+
+    @Test
+    fun faceAuthIsSupportedOnlyWhenDevicePostureMatchesConfigValue() =
+        testScope.runTest {
+            overrideResource(
+                R.integer.config_face_auth_supported_posture,
+                DevicePostureController.DEVICE_POSTURE_FLIPPED
+            )
+
+            createBiometricSettingsRepository()
+
+            val isFaceAuthSupported =
+                collectLastValue(underTest.isFaceAuthSupportedInCurrentPosture)
+
+            assertThat(isFaceAuthSupported()).isFalse()
+
+            devicePostureRepository.setCurrentPosture(DevicePosture.CLOSED)
+            assertThat(isFaceAuthSupported()).isFalse()
+
+            devicePostureRepository.setCurrentPosture(DevicePosture.HALF_OPENED)
+            assertThat(isFaceAuthSupported()).isFalse()
+
+            devicePostureRepository.setCurrentPosture(DevicePosture.OPENED)
+            assertThat(isFaceAuthSupported()).isFalse()
+
+            devicePostureRepository.setCurrentPosture(DevicePosture.UNKNOWN)
+            assertThat(isFaceAuthSupported()).isFalse()
+
+            devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
+            assertThat(isFaceAuthSupported()).isTrue()
+        }
+
     private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
         authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
new file mode 100644
index 0000000..bd6b7a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DevicePostureRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: DevicePostureRepository
+    private lateinit var testScope: TestScope
+    @Mock private lateinit var devicePostureController: DevicePostureController
+    @Captor private lateinit var callback: ArgumentCaptor<DevicePostureController.Callback>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        underTest = DevicePostureRepositoryImpl(postureController = devicePostureController)
+    }
+
+    @Test
+    fun postureChangesArePropagated() =
+        testScope.runTest {
+            whenever(devicePostureController.devicePosture)
+                .thenReturn(DevicePostureController.DEVICE_POSTURE_FLIPPED)
+            val currentPosture = collectLastValue(underTest.currentDevicePosture)
+            assertThat(currentPosture()).isEqualTo(DevicePosture.FLIPPED)
+
+            verify(devicePostureController).addCallback(callback.capture())
+
+            callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_UNKNOWN)
+            assertThat(currentPosture()).isEqualTo(DevicePosture.UNKNOWN)
+
+            callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED)
+            assertThat(currentPosture()).isEqualTo(DevicePosture.CLOSED)
+
+            callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED)
+            assertThat(currentPosture()).isEqualTo(DevicePosture.HALF_OPENED)
+
+            callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED)
+            assertThat(currentPosture()).isEqualTo(DevicePosture.OPENED)
+
+            callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED)
+            assertThat(currentPosture()).isEqualTo(DevicePosture.FLIPPED)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8bd8be5..c727b3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -308,7 +308,7 @@
                     TestConfig(
                         isVisible = true,
                         isClickable = false,
-                        isActivated = true,
+                        isActivated = false,
                         icon = icon,
                         canShowWhileLocked = false,
                         intent = Intent("action"),
@@ -363,7 +363,7 @@
                     TestConfig(
                         isVisible = true,
                         isClickable = false,
-                        isActivated = true,
+                        isActivated = false,
                         icon = icon,
                         canShowWhileLocked = false,
                         intent = Intent("action"),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 8c54da1..ab0669a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -139,6 +139,7 @@
     @Mock private lateinit var logger: MediaUiEventLogger
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
+    lateinit var remoteCastNotification: StatusBarNotification
     @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
     private val clock = FakeSystemClock()
     @Mock private lateinit var tunerService: TunerService
@@ -207,6 +208,20 @@
                 }
                 build()
             }
+        remoteCastNotification =
+            SbnBuilder().run {
+                setPkg(SYSTEM_PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(
+                        MediaStyle().apply {
+                            setMediaSession(session.sessionToken)
+                            setRemotePlaybackInfo("Remote device", 0, null)
+                        }
+                    )
+                }
+                build()
+            }
         metadataBuilder =
             MediaMetadata.Builder().apply {
                 putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
@@ -247,6 +262,7 @@
         whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
+        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
         whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
         whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
     }
@@ -400,33 +416,8 @@
 
     @Test
     fun testOnNotificationAdded_isRcn_markedRemote() {
-        val rcn =
-            SbnBuilder().run {
-                setPkg(SYSTEM_PACKAGE_NAME)
-                modifyNotification(context).also {
-                    it.setSmallIcon(android.R.drawable.ic_media_pause)
-                    it.setStyle(
-                        MediaStyle().apply {
-                            setMediaSession(session.sessionToken)
-                            setRemotePlaybackInfo("Remote device", 0, null)
-                        }
-                    )
-                }
-                build()
-            }
+        addNotificationAndLoad(remoteCastNotification)
 
-        mediaDataManager.onNotificationAdded(KEY, rcn)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
         assertThat(mediaDataCaptor.value!!.playbackLocation)
             .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE)
         verify(logger)
@@ -710,6 +701,56 @@
     }
 
     @Test
+    fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
+        // With the flag enabled to allow remote media to resume
+        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+
+        // GIVEN that the manager has a notification with a resume action, but is not local
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        whenever(playbackInfo.playbackType)
+            .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        val dataRemoteWithResume =
+            data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
+
+        // WHEN the notification is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the media data is converted to a resume state
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+    }
+
+    @Test
+    fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
+        // With the flag enabled to allow remote media to resume
+        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+
+        // GIVEN that the manager has a remote cast notification
+        addNotificationAndLoad(remoteCastNotification)
+        val data = mediaDataCaptor.value
+        assertThat(data.playbackLocation).isEqualTo(MediaData.PLAYBACK_CAST_REMOTE)
+        val dataRemoteWithResume = data.copy(resumeAction = Runnable {})
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
+
+        // WHEN the RCN is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the media data is removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
     fun testOnNotificationRemoved_withResumption_tooManyPlayers() {
         // Given the maximum number of resume controls already
         val desc =
@@ -1654,22 +1695,7 @@
             )
 
         // update to remote cast
-        val rcn =
-            SbnBuilder().run {
-                setPkg(SYSTEM_PACKAGE_NAME) // System package
-                modifyNotification(context).also {
-                    it.setSmallIcon(android.R.drawable.ic_media_pause)
-                    it.setStyle(
-                        MediaStyle().apply {
-                            setMediaSession(session.sessionToken)
-                            setRemotePlaybackInfo("Remote device", 0, null)
-                        }
-                    )
-                }
-                build()
-            }
-
-        mediaDataManager.onNotificationAdded(KEY, rcn)
+        mediaDataManager.onNotificationAdded(KEY, remoteCastNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(logger)
@@ -2038,9 +2064,14 @@
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
 
-    /** Helper function to add a media notification and capture the resulting MediaData */
+    /** Helper function to add a basic media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        addNotificationAndLoad(mediaNotification)
+    }
+
+    /** Helper function to add the given notification and capture the resulting MediaData */
+    private fun addNotificationAndLoad(sbn: StatusBarNotification) {
+        mediaDataManager.onNotificationAdded(KEY, sbn)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 136ace1..4dfa626 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -92,6 +93,7 @@
     @Mock private lateinit var mockContext: Context
     @Mock private lateinit var pendingIntent: PendingIntent
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var mediaFlags: MediaFlags
 
     @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
     @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
@@ -134,6 +136,7 @@
         whenever(mockContext.packageManager).thenReturn(context.packageManager)
         whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
         whenever(mockContext.userId).thenReturn(context.userId)
+        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
 
         executor = FakeExecutor(clock)
         resumeListener =
@@ -146,7 +149,8 @@
                 tunerService,
                 resumeBrowserFactory,
                 dumpManager,
-                clock
+                clock,
+                mediaFlags,
             )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
@@ -188,7 +192,8 @@
                 tunerService,
                 resumeBrowserFactory,
                 dumpManager,
-                clock
+                clock,
+                mediaFlags,
             )
         listener.setManager(mediaDataManager)
         verify(broadcastDispatcher, never())
@@ -244,6 +249,32 @@
     }
 
     @Test
+    fun testOnLoad_localCast_remoteResumeAllowed_doesCheck() {
+        // If local cast media is allowed to resume
+        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+
+        // When media data is loaded that has not been checked yet, and is a local cast
+        val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
+        resumeListener.onMediaDataLoaded(KEY, null, dataCast)
+
+        // Then we report back to the manager
+        verify(mediaDataManager).setResumeAction(KEY, null)
+    }
+
+    @Test
+    fun testOnLoad_remoteCast_remoteResumeAllowed_doesCheck() {
+        // If local cast media is allowed to resume
+        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+
+        // When media data is loaded that has not been checked yet, and is a remote cast
+        val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE)
+        resumeListener.onMediaDataLoaded(KEY, null, dataRcn)
+
+        // Then we do not take action
+        verify(mediaDataManager, never()).setResumeAction(any(), any())
+    }
+
+    @Test
     fun testOnLoad_checksForResume_hasService() {
         setUpMbsWithValidResolveInfo()
 
@@ -389,7 +420,8 @@
                 tunerService,
                 resumeBrowserFactory,
                 dumpManager,
-                clock
+                clock,
+                mediaFlags,
             )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
@@ -421,7 +453,8 @@
                 tunerService,
                 resumeBrowserFactory,
                 dumpManager,
-                clock
+                clock,
+                mediaFlags,
             )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
@@ -463,7 +496,8 @@
                 tunerService,
                 resumeBrowserFactory,
                 dumpManager,
-                clock
+                clock,
+                mediaFlags,
             )
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index aacbf8f..0a91a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -143,8 +143,8 @@
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultTrue() {
-        // Tablets may be using taskbar and the logic is different
-        mNavigationBarController.mIsTablet = false;
+        // Large screens may be using taskbar and the logic is different
+        mNavigationBarController.mIsLargeScreen = false;
         doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(true, null);
@@ -292,7 +292,7 @@
     @Test
     public void testConfigurationChange_taskbarNotInitialized() {
         Configuration configuration = mContext.getResources().getConfiguration();
-        when(Utilities.isTablet(any())).thenReturn(true);
+        when(Utilities.isLargeScreen(any())).thenReturn(true);
         mNavigationBarController.onConfigChanged(configuration);
         verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
     }
@@ -300,7 +300,7 @@
     @Test
     public void testConfigurationChange_taskbarInitialized() {
         Configuration configuration = mContext.getResources().getConfiguration();
-        when(Utilities.isTablet(any())).thenReturn(true);
+        when(Utilities.isLargeScreen(any())).thenReturn(true);
         when(mTaskbarDelegate.isInitialized()).thenReturn(true);
         mNavigationBarController.onConfigChanged(configuration);
         verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 26eff61..1fdb364 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -35,7 +35,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import org.json.JSONException
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -271,10 +270,14 @@
 
     @Test
     fun jsonDeserialization_gotExpectedObject() {
-        val expected = ClockSettings("ID", null).apply { _applied_timestamp = 500 }
+        val expected = ClockSettings("ID", null).apply {
+            metadata.put("appliedTimestamp", 500)
+        }
         val actual = ClockSettings.deserialize("""{
             "clockId":"ID",
-            "_applied_timestamp":500
+            "metadata": {
+                "appliedTimestamp":500
+            }
         }""")
         assertEquals(expected, actual)
     }
@@ -291,29 +294,32 @@
         val expected = ClockSettings("ID", null)
         val actual = ClockSettings.deserialize("""{
             "clockId":"ID",
-            "_applied_timestamp":null
+            "metadata":null
         }""")
         assertEquals(expected, actual)
     }
 
-    @Test(expected = JSONException::class)
-    fun jsonDeserialization_noId_threwException() {
-        val expected = ClockSettings(null, null).apply { _applied_timestamp = 500 }
-        val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}")
+    @Test
+    fun jsonDeserialization_noId_deserializedEmpty() {
+        val expected = ClockSettings(null, null).apply {
+            metadata.put("appliedTimestamp", 500)
+        }
+        val actual = ClockSettings.deserialize("{\"metadata\":{\"appliedTimestamp\":500}}")
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonSerialization_gotExpectedString() {
-        val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null)
-            .apply { _applied_timestamp = 500 })
+        val expected = "{\"clockId\":\"ID\",\"metadata\":{\"appliedTimestamp\":500}}"
+        val actual = ClockSettings.serialize(ClockSettings("ID", null).apply {
+            metadata.put("appliedTimestamp", 500)
+        })
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonSerialization_noTimestamp_gotExpectedString() {
-        val expected = "{\"clockId\":\"ID\"}"
+        val expected = "{\"clockId\":\"ID\",\"metadata\":{}}"
         val actual = ClockSettings.serialize(ClockSettings("ID", null))
         assertEquals(expected, actual)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
new file mode 100644
index 0000000..7a67796
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.statusbar.notification
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.statusbar.StatusBarState
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() {
+
+    private val logBufferCounter = LogBufferCounter()
+    private lateinit var logger: NotificationWakeUpCoordinatorLogger
+
+    private fun verifyDidLog(times: Int) {
+        logBufferCounter.verifyDidLog(times)
+    }
+
+    @Before
+    fun setup() {
+        logger = NotificationWakeUpCoordinatorLogger(logBufferCounter.logBuffer)
+    }
+
+    @Test
+    fun setDozeAmountWillThrottleFractionalUpdates() {
+        logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+        verifyDidLog(1)
+        logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(1)
+        logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(0)
+        logger.logSetDozeAmount(1f, 1f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(1)
+    }
+
+    @Test
+    fun setDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
+        logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+        verifyDidLog(1)
+        logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(1)
+        logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(0)
+        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false)
+        verifyDidLog(1)
+    }
+
+    @Test
+    fun setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() {
+        logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+        verifyDidLog(1)
+        logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(1)
+        logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
+        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+        verifyDidLog(0)
+        logger.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false)
+        verifyDidLog(1)
+    }
+
+    class LogBufferCounter {
+        val recentLogs = mutableListOf<Pair<String, LogLevel>>()
+        val tracker =
+            object : LogcatEchoTracker {
+                override val logInBackgroundThread: Boolean = false
+                override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false
+                override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
+                    recentLogs.add(tagName to level)
+                    return true
+                }
+            }
+        val logBuffer =
+            LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false)
+
+        fun verifyDidLog(times: Int) {
+            assertThat(recentLogs).hasSize(times)
+            recentLogs.clear()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 305b9fe..6b18169 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone
 
 import android.app.AlarmManager
-import android.app.IActivityManager
 import android.app.admin.DevicePolicyManager
 import android.content.SharedPreferences
 import android.os.UserManager
@@ -87,7 +86,6 @@
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var locationController: LocationController
     @Mock private lateinit var sensorPrivacyController: SensorPrivacyController
-    @Mock private lateinit var iActivityManager: IActivityManager
     @Mock private lateinit var alarmManager: AlarmManager
     @Mock private lateinit var userManager: UserManager
     @Mock private lateinit var userTracker: UserTracker
@@ -176,6 +174,7 @@
             commandQueue,
             broadcastDispatcher,
             executor,
+            executor,
             testableLooper.looper,
             context.resources,
             castController,
@@ -190,7 +189,6 @@
             keyguardStateController,
             locationController,
             sensorPrivacyController,
-            iActivityManager,
             alarmManager,
             userManager,
             userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 5288608..0413d92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.util.TestFoldStateProvider
 import org.junit.Before
 import org.junit.Test
@@ -50,7 +49,7 @@
         runOnMainThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+            { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
             { foldStateProvider.sendHingeAngleUpdate(180f) },
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
@@ -67,7 +66,7 @@
         runOnMainThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+            { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
             { foldStateProvider.sendHingeAngleUpdate(180f) },
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
@@ -84,7 +83,7 @@
             { foldStateProvider.sendHingeAngleUpdate(90f) },
             { foldStateProvider.sendHingeAngleUpdate(180f) },
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+            { foldStateProvider.sendUnfoldedScreenAvailable() },
         )
 
         with(listener.ensureTransitionFinished()) {
@@ -113,7 +112,7 @@
     fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
         runOnMainThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+            { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) },
@@ -129,7 +128,7 @@
     fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() {
         runOnMainThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+            { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
             {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 6086e16..8476d0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
 import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
 import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
+import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
@@ -71,6 +72,7 @@
 
     private val foldUpdates: MutableList<Int> = arrayListOf()
     private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
+    private val unfoldedScreenAvailabilityUpdates: MutableList<Unit> = arrayListOf()
 
     private var scheduledRunnable: Runnable? = null
     private var scheduledRunnableDelay: Long? = null
@@ -106,6 +108,10 @@
                 override fun onFoldUpdate(update: Int) {
                     foldUpdates.add(update)
                 }
+
+                override fun onUnfoldedScreenAvailable() {
+                    unfoldedScreenAvailabilityUpdates.add(Unit)
+                }
             })
         foldStateProvider.start()
 
@@ -156,8 +162,8 @@
         sendHingeAngleEvent(10)
         screenOnStatusProvider.notifyScreenTurnedOn()
 
-        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
-                FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
+        assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
     }
 
     @Test
@@ -174,8 +180,9 @@
         sendHingeAngleEvent(40)
         sendHingeAngleEvent(10)
 
-        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
-                FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING)
+        assertThat(foldUpdates)
+                .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
+        assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
     }
 
     @Test
@@ -223,7 +230,7 @@
 
         fireScreenOnEvent()
 
-        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+        assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
     }
 
     @Test
@@ -277,7 +284,7 @@
 
     @Test
     fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() {
-        sendHingeAngleEvent(90)
+        setInitialHingeAngle(90)
         sendHingeAngleEvent(80)
 
         simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS)
@@ -288,7 +295,7 @@
 
     @Test
     fun startClosingEvent_beforeTimeout_abortNotEmitted() {
-        sendHingeAngleEvent(90)
+        setInitialHingeAngle(90)
         sendHingeAngleEvent(80)
 
         simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1)
@@ -298,7 +305,7 @@
 
     @Test
     fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
         sendHingeAngleEvent(90)
 
         simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1)
@@ -309,7 +316,7 @@
 
     @Test
     fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() {
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
         sendHingeAngleEvent(90)
 
         // The timeout should not trigger here.
@@ -323,7 +330,7 @@
 
     @Test
     fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(90)
         sendHingeAngleEvent(80)
@@ -334,20 +341,19 @@
     @Test
     fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
         val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
-        for (i in 1..maxAngle) {
-            foldUpdates.clear()
-
-            simulateFolding(startAngle = i)
+        val minAngle = Math.ceil(HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toDouble()).toInt() + 1
+        for (startAngle in minAngle..maxAngle) {
+            setInitialHingeAngle(startAngle)
+            sendHingeAngleEvent(startAngle - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
 
             assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
-            simulateTimeout() // Timeout to set the state to aborted.
         }
     }
 
     @Test
     fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
         setupForegroundActivityType(isHomeActivity = false)
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
 
@@ -357,7 +363,7 @@
     @Test
     fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() {
         setupForegroundActivityType(isHomeActivity = null)
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
 
@@ -367,7 +373,7 @@
     @Test
     fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() {
         setupForegroundActivityType(isHomeActivity = true)
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
 
@@ -377,9 +383,11 @@
     @Test
     fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
         setupForegroundActivityType(isHomeActivity = false)
-        sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
+        setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
 
-        sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
+        sendHingeAngleEvent(
+                START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
+                        HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
 
         assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
     }
@@ -388,7 +396,7 @@
     fun startClosingEvent_whileNotOnKeyguardAndNotOnLauncher_doesNotTriggerBeforeThreshold() {
         setKeyguardVisibility(visible = false)
         setupForegroundActivityType(isHomeActivity = false)
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
 
@@ -398,7 +406,7 @@
     @Test
     fun startClosingEvent_whileKeyguardStateNotAvailable_triggerBeforeThreshold() {
         setKeyguardVisibility(visible = null)
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
 
@@ -408,7 +416,7 @@
     @Test
     fun startClosingEvent_whileonKeyguard_doesTriggerBeforeThreshold() {
         setKeyguardVisibility(visible = true)
-        sendHingeAngleEvent(180)
+        setInitialHingeAngle(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
 
@@ -418,9 +426,59 @@
     @Test
     fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() {
         setKeyguardVisibility(visible = false)
-        sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
+        setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
 
-        sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
+        sendHingeAngleEvent(
+                START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
+                        HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_doesNotTriggerBelowThreshold() {
+        val thresholdAngle = (FULLY_OPEN_DEGREES - FULLY_OPEN_THRESHOLD_DEGREES).toInt()
+        setInitialHingeAngle(180)
+        sendHingeAngleEvent(thresholdAngle + 1)
+
+        assertThat(foldUpdates).isEmpty()
+    }
+
+    @Test
+    fun startClosingEvent_triggersAfterThreshold() {
+        val thresholdAngle = (FULLY_OPEN_DEGREES - FULLY_OPEN_THRESHOLD_DEGREES).toInt()
+        setInitialHingeAngle(180)
+        sendHingeAngleEvent(thresholdAngle + 1)
+        sendHingeAngleEvent(thresholdAngle - 1)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_triggersAfterThreshold_fromHalfOpen() {
+        setInitialHingeAngle(120)
+        sendHingeAngleEvent((120 - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES + 1).toInt())
+        assertThat(foldUpdates).isEmpty()
+        sendHingeAngleEvent((120 - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES - 1).toInt())
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startOpeningAndClosingEvents_triggerWithOpenAndClose() {
+        setInitialHingeAngle(120)
+        sendHingeAngleEvent(130)
+        sendHingeAngleEvent(120)
+        assertThat(foldUpdates)
+                .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_notInterrupted_whenAngleIsSlightlyIncreased() {
+        setInitialHingeAngle(120)
+        sendHingeAngleEvent(110)
+        sendHingeAngleEvent(111)
+        sendHingeAngleEvent(100)
 
         assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
     }
@@ -504,11 +562,6 @@
         }
     }
 
-    private fun simulateFolding(startAngle: Int) {
-        sendHingeAngleEvent(startAngle)
-        sendHingeAngleEvent(startAngle - 1)
-    }
-
     private fun setFoldState(folded: Boolean) {
         foldProvider.notifyFolded(folded)
     }
@@ -521,6 +574,17 @@
         testHingeAngleProvider.notifyAngle(angle.toFloat())
     }
 
+    private fun setInitialHingeAngle(angle: Int) {
+        setFoldState(angle == 0)
+        sendHingeAngleEvent(angle)
+        if (scheduledRunnableDelay != null) {
+            simulateTimeout()
+        }
+        hingeAngleUpdates.clear()
+        foldUpdates.clear()
+        unfoldedScreenAvailabilityUpdates.clear()
+    }
+
     private class TestFoldProvider : FoldProvider {
         private val callbacks = arrayListOf<FoldCallback>()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
index a064e8c..fbb0e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
@@ -57,4 +57,8 @@
     fun sendHingeAngleUpdate(angle: Float) {
         listeners.forEach { it.onHingeAngleUpdate(angle) }
     }
+
+    fun sendUnfoldedScreenAvailable() {
+        listeners.forEach { it.onUnfoldedScreenAvailable() }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index ccf378a..ddd880b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,10 +17,7 @@
 
 package com.android.systemui.user.data.repository
 
-import android.app.IActivityManager
-import android.app.UserSwitchObserver
 import android.content.pm.UserInfo
-import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
@@ -44,14 +41,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyString
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -60,8 +51,6 @@
 class UserRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var manager: UserManager
-    @Mock private lateinit var activityManager: IActivityManager
-    @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver>
 
     private lateinit var underTest: UserRepositoryImpl
 
@@ -229,30 +218,31 @@
     }
 
     @Test
-    fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest {
+    fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest {
         underTest = create(this)
 
         underTest.userSwitchingInProgress.launchIn(this)
         underTest.userSwitchingInProgress.launchIn(this)
         underTest.userSwitchingInProgress.launchIn(this)
 
-        verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString())
+        // Two callbacks registered - one for observing user switching and one for observing the
+        // selected user
+        assertThat(tracker.callbacks.size).isEqualTo(2)
     }
 
     @Test
-    fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest {
+    fun userSwitchingInProgress_propagatesStateFromUserTracker() = runSelfCancelingTest {
         underTest = create(this)
-        verify(activityManager)
-            .registerUserSwitchObserver(userSwitchObserver.capture(), anyString())
+        assertThat(tracker.callbacks.size).isEqualTo(2)
 
-        userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java))
+        tracker.onUserChanging(0)
 
         var mostRecentSwitchingValue = false
         underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this)
 
         assertThat(mostRecentSwitchingValue).isTrue()
 
-        userSwitchObserver.value.onUserSwitchComplete(0)
+        tracker.onUserChanged(0)
         assertThat(mostRecentSwitchingValue).isFalse()
     }
 
@@ -332,7 +322,6 @@
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
-            activityManager = activityManager,
             featureFlags = featureFlags,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 01dac36..d4b1701 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,6 +21,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOf
 
 class FakeBiometricSettingsRepository : BiometricSettingsRepository {
 
@@ -42,6 +43,9 @@
     override val isFingerprintEnabledByDevicePolicy =
         _isFingerprintEnabledByDevicePolicy.asStateFlow()
 
+    override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
+        get() = flowOf(true)
+
     fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
         _isFingerprintEnrolled.value = isFingerprintEnrolled
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt
new file mode 100644
index 0000000..914c786
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.model.DevicePosture
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeDevicePostureRepository : DevicePostureRepository {
+    private val _currentDevicePosture = MutableStateFlow(DevicePosture.UNKNOWN)
+    override val currentDevicePosture: Flow<DevicePosture>
+        get() = _currentDevicePosture
+
+    fun setCurrentPosture(posture: DevicePosture) {
+        _currentDevicePosture.value = posture
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 251014f..4242c16 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -22,6 +22,7 @@
 import android.os.UserHandle
 import android.test.mock.MockContentResolver
 import com.android.systemui.util.mockito.mock
+import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 
 /** A fake [UserTracker] to be used in tests. */
@@ -66,11 +67,19 @@
         _userId = _userInfo.id
         _userHandle = UserHandle.of(_userId)
 
+        onUserChanging()
+        onUserChanged()
+    }
+
+    fun onUserChanging(userId: Int = _userId) {
         val copy = callbacks.toList()
-        copy.forEach {
-            it.onUserChanging(_userId, userContext)
-            it.onUserChanged(_userId, userContext)
-        }
+        val latch = CountDownLatch(copy.size)
+        copy.forEach { it.onUserChanging(userId, userContext, latch) }
+    }
+
+    fun onUserChanged(userId: Int = _userId) {
+        val copy = callbacks.toList()
+        copy.forEach { it.onUserChanged(userId, userContext) }
     }
 
     fun onProfileChanged() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
new file mode 100644
index 0000000..0c9ce0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.DialogInterface
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.verify
+import org.mockito.stubbing.Stubber
+
+class FakeSystemUIDialogController {
+
+    val dialog: SystemUIDialog = mock()
+
+    private val clickListeners: MutableMap<Int, DialogInterface.OnClickListener> = mutableMapOf()
+
+    init {
+        saveListener(DialogInterface.BUTTON_POSITIVE)
+            .whenever(dialog)
+            .setPositiveButton(any(), any())
+        saveListener(DialogInterface.BUTTON_POSITIVE)
+            .whenever(dialog)
+            .setPositiveButton(any(), any(), any())
+
+        saveListener(DialogInterface.BUTTON_NEGATIVE)
+            .whenever(dialog)
+            .setNegativeButton(any(), any())
+        saveListener(DialogInterface.BUTTON_NEGATIVE)
+            .whenever(dialog)
+            .setNegativeButton(any(), any(), any())
+
+        saveListener(DialogInterface.BUTTON_NEUTRAL).whenever(dialog).setNeutralButton(any(), any())
+        saveListener(DialogInterface.BUTTON_NEUTRAL)
+            .whenever(dialog)
+            .setNeutralButton(any(), any(), any())
+    }
+
+    fun clickNegative() {
+        performClick(DialogInterface.BUTTON_NEGATIVE, "This dialog has no negative button")
+    }
+
+    fun clickPositive() {
+        performClick(DialogInterface.BUTTON_POSITIVE, "This dialog has no positive button")
+    }
+
+    fun clickNeutral() {
+        performClick(DialogInterface.BUTTON_NEUTRAL, "This dialog has no neutral button")
+    }
+
+    fun cancel() {
+        val captor = ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+        verify(dialog).setOnCancelListener(captor.capture())
+        captor.value.onCancel(dialog)
+    }
+
+    private fun performClick(which: Int, errorMessage: String) {
+        clickListeners
+            .getOrElse(which) { throw IllegalAccessException(errorMessage) }
+            .onClick(dialog, which)
+    }
+
+    private fun saveListener(which: Int): Stubber = doAnswer {
+        val listener = it.getArgument<DialogInterface.OnClickListener>(1)
+        clickListeners[which] = listener
+        Unit
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index 4622464..c437e5c 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import javax.inject.Inject
@@ -59,12 +58,15 @@
     }
 
     override fun onFoldUpdate(@FoldUpdate update: Int) {
-        when (update) {
-            FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start()
-            FOLD_UPDATE_FINISH_CLOSED -> animator.cancel()
+        if (update == FOLD_UPDATE_FINISH_CLOSED) {
+             animator.cancel()
         }
     }
 
+    override fun onUnfoldedScreenAvailable() {
+        animator.start()
+    }
+
     override fun addCallback(listener: TransitionProgressListener) {
         listeners.add(listener)
     }
@@ -73,8 +75,6 @@
         listeners.remove(listener)
     }
 
-    override fun onHingeAngleUpdate(angle: Float) {}
-
     private object AnimationProgressProperty :
         FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") {
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 6ffbe5a..d19b414 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
@@ -78,21 +77,11 @@
 
     override fun onFoldUpdate(@FoldUpdate update: Int) {
         when (update) {
-            FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
-                startTransition(startValue = 0f)
-
-                // Stop the animation if the device has already opened by the time when
-                // the display is available as we won't receive the full open event anymore
-                if (foldStateProvider.isFinishedOpening) {
-                    cancelTransition(endValue = 1f, animate = true)
-                }
-            }
             FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> {
                 // Do not cancel if we haven't started the transition yet.
                 // This could happen when we fully unfolded the device before the screen
                 // became available. In this case we start and immediately cancel the animation
-                // in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to
-                // cancel it here.
+                // in onUnfoldedScreenAvailable event handler, so we don't need to cancel it here.
                 if (isTransitionRunning) {
                     cancelTransition(endValue = 1f, animate = true)
                 }
@@ -125,6 +114,16 @@
         }
     }
 
+    override fun onUnfoldedScreenAvailable() {
+        startTransition(startValue = 0f)
+
+        // Stop the animation if the device has already opened by the time when
+        // the display is available as we won't receive the full open event anymore
+        if (foldStateProvider.isFinishedOpening) {
+            cancelTransition(endValue = 1f, animate = true)
+        }
+    }
+
     private fun cancelTransition(endValue: Float, animate: Boolean) {
         if (isTransitionRunning && animate) {
             if (endValue == 1.0f && !isAnimatedCancelRunning) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 97c9ba9..82fd225 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -54,6 +54,7 @@
     @FoldUpdate private var lastFoldUpdate: Int? = null
 
     @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
+    @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f
 
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
@@ -112,29 +113,45 @@
 
     private fun onHingeAngle(angle: Float) {
         if (DEBUG) {
-            Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle")
+            Log.d(
+                TAG,
+                "Hinge angle: $angle, " +
+                    "lastHingeAngle: $lastHingeAngle, " +
+                    "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
+            )
             Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt())
         }
 
-        val isClosing = angle < lastHingeAngle
+        val currentDirection =
+                if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
+        if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
+            lastHingeAngleBeforeTransition = lastHingeAngle
+        }
+
+        val isClosing = angle < lastHingeAngleBeforeTransition
+        val transitionUpdate =
+                if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
+        val angleChangeSurpassedThreshold =
+            Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
-        val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+        val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate
         val screenAvailableEventSent = isUnfoldHandled
 
-        if (isClosing // hinge angle should be decreasing since last update
-                && !closingEventDispatched  // we haven't sent closing event already
-                && !isFullyOpened // do not send closing event if we are in fully opened hinge
+        if (
+            angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
+                eventNotAlreadyDispatched && // we haven't sent transition event already
+                !isFullyOpened && // do not send transition event if we are in fully opened hinge
                                   // angle range as closing threshold could overlap this range
-                && screenAvailableEventSent // do not send closing event if we are still in
-                                            // the process of turning on the inner display
-                && isClosingThresholdMet(angle) // hinge angle is below certain threshold.
+                screenAvailableEventSent && // do not send transition event if we are still in the
+                                            // process of turning on the inner display
+                isClosingThresholdMet(angle) // hinge angle is below certain threshold.
         ) {
-            notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
+            notifyFoldUpdate(transitionUpdate, lastHingeAngle)
         }
 
         if (isTransitionInProgress) {
             if (isFullyOpened) {
-                notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN, angle)
                 cancelTimeout()
             } else {
                 // The timeout will trigger some constant time after the last angle update.
@@ -146,7 +163,7 @@
         outputListeners.forEach { it.onHingeAngleUpdate(angle) }
     }
 
-    private fun isClosingThresholdMet(currentAngle: Float) : Boolean {
+    private fun isClosingThresholdMet(currentAngle: Float): Boolean {
         val closingThreshold = getClosingThreshold()
         return closingThreshold == null || currentAngle < closingThreshold
     }
@@ -179,23 +196,29 @@
 
             if (isFolded) {
                 hingeAngleProvider.stop()
-                notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED, lastHingeAngle)
                 cancelTimeout()
                 isUnfoldHandled = false
             } else {
-                notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+                notifyFoldUpdate(FOLD_UPDATE_START_OPENING, lastHingeAngle)
                 rescheduleAbortAnimationTimeout()
                 hingeAngleProvider.start()
             }
         }
     }
 
-    private fun notifyFoldUpdate(@FoldUpdate update: Int) {
+    private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) {
         if (DEBUG) {
             Log.d(TAG, update.name())
         }
+        val previouslyTransitioning = isTransitionInProgress
+
         outputListeners.forEach { it.onFoldUpdate(update) }
         lastFoldUpdate = update
+
+        if (previouslyTransitioning != isTransitionInProgress) {
+            lastHingeAngleBeforeTransition = angle
+        }
     }
 
     private fun rescheduleAbortAnimationTimeout() {
@@ -209,7 +232,8 @@
         handler.removeCallbacks(timeoutRunnable)
     }
 
-    private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+    private fun cancelAnimation(): Unit =
+            notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
 
     private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
 
@@ -221,7 +245,7 @@
             // receive 'folded' event. If SystemUI started when device is already folded it will
             // still receive 'folded' event on startup.
             if (!isFolded && !isUnfoldHandled) {
-                outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }
+                outputListeners.forEach { it.onUnfoldedScreenAvailable() }
                 isUnfoldHandled = true
             }
         }
@@ -257,7 +281,6 @@
     when (this) {
         FOLD_UPDATE_START_OPENING -> "START_OPENING"
         FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
-        FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
         FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN"
         FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN"
         FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
@@ -270,5 +293,8 @@
 /** Threshold after which we consider the device fully unfolded. */
 @VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
 
+/** Threshold after which hinge angle updates are considered. This is to eliminate noise. */
+@VisibleForTesting const val HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES = 7.5f
+
 /** Fold animation on top of apps only when the angle exceeds this threshold. */
 @VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index c7a8bf3..0af372f 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -31,8 +31,9 @@
     val isFinishedOpening: Boolean
 
     interface FoldUpdatesListener {
-        fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float)
-        fun onFoldUpdate(@FoldUpdate update: Int)
+        @JvmDefault fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {}
+        @JvmDefault fun onFoldUpdate(@FoldUpdate update: Int) {}
+        @JvmDefault fun onUnfoldedScreenAvailable() {}
     }
 
     @IntDef(
@@ -40,7 +41,6 @@
             [
                 FOLD_UPDATE_START_OPENING,
                 FOLD_UPDATE_START_CLOSING,
-                FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
                 FOLD_UPDATE_FINISH_HALF_OPEN,
                 FOLD_UPDATE_FINISH_FULL_OPEN,
                 FOLD_UPDATE_FINISH_CLOSED])
@@ -50,7 +50,6 @@
 
 const val FOLD_UPDATE_START_OPENING = 0
 const val FOLD_UPDATE_START_CLOSING = 1
-const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 2
-const val FOLD_UPDATE_FINISH_HALF_OPEN = 3
-const val FOLD_UPDATE_FINISH_FULL_OPEN = 4
-const val FOLD_UPDATE_FINISH_CLOSED = 5
+const val FOLD_UPDATE_FINISH_HALF_OPEN = 2
+const val FOLD_UPDATE_FINISH_FULL_OPEN = 3
+const val FOLD_UPDATE_FINISH_CLOSED = 4
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index f74356d..ccab7bc9 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -42,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 /**
  * Internal controller for starting and stopping the current dream and managing related state.
@@ -119,10 +120,19 @@
                     + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
                     + ", userId=" + userId + ", reason='" + reason + "'");
 
-            if (mCurrentDream != null) {
-                mPreviousDreams.add(mCurrentDream);
-            }
+            final DreamRecord oldDream = mCurrentDream;
             mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
+            if (oldDream != null) {
+                if (Objects.equals(oldDream.mName, mCurrentDream.mName)) {
+                    // We are attempting to start a dream that is currently waking up gently.
+                    // Let's silently stop the old instance here to clear the dream state.
+                    // This should happen after the new mCurrentDream is set to avoid announcing
+                    // a "dream stopped" state.
+                    stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream);
+                } else {
+                    mPreviousDreams.add(oldDream);
+                }
+            }
 
             mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime();
             MetricsLogger.visible(mContext,
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 632a34e..9212331 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -430,6 +430,7 @@
             @NonNull List<ShortcutInfo> changedShortcuts) {
         Preconditions.checkArgument(newShortcut.isEnabled(),
                 "pushDynamicShortcuts() cannot publish disabled shortcuts");
+        ensureShortcutCountBeforePush();
 
         newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
 
@@ -437,7 +438,7 @@
         final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
         boolean deleted = false;
 
-        if (oldShortcut == null) {
+        if (oldShortcut == null || !oldShortcut.isDynamic()) {
             final ShortcutService service = mShortcutUser.mService;
             final int maxShortcuts = service.getMaxActivityShortcuts();
 
@@ -446,18 +447,12 @@
             final ArrayList<ShortcutInfo> activityShortcuts = all.get(newShortcut.getActivity());
 
             if (activityShortcuts != null && activityShortcuts.size() > maxShortcuts) {
-                Slog.e(TAG, "Error pushing shortcut. There are already "
-                        + activityShortcuts.size() + " shortcuts, exceeding the " + maxShortcuts
-                        + " shortcuts limit when pushing the new shortcut " + newShortcut
-                        + ". Id of shortcuts currently available in system memory are "
-                        + activityShortcuts.stream().map(ShortcutInfo::getId)
-                        .collect(Collectors.joining(",", "[", "]")));
-                // TODO: This should not have happened. If it does, identify the root cause where
-                //  possible, otherwise bail-out early to prevent memory issue.
+                // Root cause was discovered in b/233155034, so this should not be happening.
+                service.wtf("Error pushing shortcut. There are already "
+                        + activityShortcuts.size() + " shortcuts.");
             }
             if (activityShortcuts != null && activityShortcuts.size() == maxShortcuts) {
                 // Max has reached. Delete the shortcut with lowest rank.
-
                 // Sort by isManifestShortcut() and getRank().
                 Collections.sort(activityShortcuts, mShortcutTypeAndRankComparator);
 
@@ -473,7 +468,8 @@
                 deleted = deleteDynamicWithId(shortcut.getId(), /* ignoreInvisible =*/ true,
                         /*ignorePersistedShortcuts=*/ true) != null;
             }
-        } else {
+        }
+        if (oldShortcut != null) {
             // It's an update case.
             // Make sure the target is updatable. (i.e. should be mutable.)
             oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
@@ -505,6 +501,32 @@
         return deleted;
     }
 
+    private void ensureShortcutCountBeforePush() {
+        final ShortcutService service = mShortcutUser.mService;
+        // Ensure the total number of shortcuts doesn't exceed the hard limit per app.
+        final int maxShortcutPerApp = service.getMaxAppShortcuts();
+        synchronized (mLock) {
+            final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si ->
+                    !si.isPinned()).collect(Collectors.toList());
+            if (appShortcuts.size() >= maxShortcutPerApp) {
+                // Max has reached. Removes shortcuts until they fall within the hard cap.
+                // Sort by isManifestShortcut(), isDynamic() and getLastChangedTimestamp().
+                Collections.sort(appShortcuts, mShortcutTypeRankAndTimeComparator);
+
+                while (appShortcuts.size() >= maxShortcutPerApp) {
+                    final ShortcutInfo shortcut = appShortcuts.remove(appShortcuts.size() - 1);
+                    if (shortcut.isDeclaredInManifest()) {
+                        // All shortcuts are manifest shortcuts and cannot be removed.
+                        throw new IllegalArgumentException(getPackageName() + " has published "
+                                + appShortcuts.size() + " manifest shortcuts across different"
+                                + " activities.");
+                    }
+                    forceDeleteShortcutInner(shortcut.getId());
+                }
+            }
+        }
+    }
+
     /**
      * Remove all shortcuts that aren't pinned, cached nor dynamic.
      *
@@ -1371,6 +1393,61 @@
     };
 
     /**
+     * To sort by isManifestShortcut(), isDynamic(), getRank() and
+     * getLastChangedTimestamp(). i.e. manifest shortcuts come before non-manifest shortcuts,
+     * dynamic shortcuts come before floating shortcuts, then sort by last changed timestamp.
+     *
+     * This is used to decide which shortcuts to remove when the total number of shortcuts retained
+     * for the app exceeds the limit defined in {@link ShortcutService#getMaxAppShortcuts()}.
+     *
+     * (Note the number of manifest shortcuts is always <= the max number, because if there are
+     * more, ShortcutParser would ignore the rest.)
+     */
+    final Comparator<ShortcutInfo> mShortcutTypeRankAndTimeComparator = (ShortcutInfo a,
+            ShortcutInfo b) -> {
+        if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
+            return -1;
+        }
+        if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
+            return 1;
+        }
+        if (a.isDynamic() && b.isDynamic()) {
+            return Integer.compare(a.getRank(), b.getRank());
+        }
+        if (a.isDynamic()) {
+            return -1;
+        }
+        if (b.isDynamic()) {
+            return 1;
+        }
+        if (a.isCached() && b.isCached()) {
+            // if both shortcuts are cached, prioritize shortcuts cached by people tile,
+            if (a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)
+                    && !b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) {
+                return -1;
+            } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)
+                    && b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) {
+                return 1;
+            }
+            // followed by bubbles.
+            if (a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)
+                    && !b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) {
+                return -1;
+            } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)
+                    && b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) {
+                return 1;
+            }
+        }
+        if (a.isCached()) {
+            return -1;
+        }
+        if (b.isCached()) {
+            return 1;
+        }
+        return Long.compare(b.getLastChangedTimestamp(), a.getLastChangedTimestamp());
+    };
+
+    /**
      * Build a list of shortcuts for each target activity and return as a map. The result won't
      * contain "floating" shortcuts because they don't belong on any activities.
      */
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 49831d7..7fc46fd 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,6 +181,9 @@
     static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
 
     @VisibleForTesting
+    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 100;
+
+    @VisibleForTesting
     static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
 
     @VisibleForTesting
@@ -257,6 +260,11 @@
         String KEY_MAX_SHORTCUTS = "max_shortcuts";
 
         /**
+         * Key name for the max shortcuts can be retained in system ram per app. (int)
+         */
+        String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
+
+        /**
          * Key name for icon compression quality, 0-100.
          */
         String KEY_ICON_QUALITY = "icon_quality";
@@ -329,11 +337,16 @@
             new SparseArray<>();
 
     /**
-     * Max number of dynamic + manifest shortcuts that each application can have at a time.
+     * Max number of dynamic + manifest shortcuts that each activity can have at a time.
      */
     private int mMaxShortcuts;
 
     /**
+     * Max number of shortcuts that can exists in system ram for each application.
+     */
+    private int mMaxShortcutsPerApp;
+
+    /**
      * Max number of updating API calls that each application can make during the interval.
      */
     int mMaxUpdatesPerInterval;
@@ -807,6 +820,9 @@
         mMaxShortcuts = Math.max(0, (int) parser.getLong(
                 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
 
+        mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
+                ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
+
         final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
                 ? (int) parser.getLong(
                 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1759,6 +1775,13 @@
     }
 
     /**
+     * Return the max number of shortcuts can be retaiend in system ram for each application.
+     */
+    int getMaxAppShortcuts() {
+        return mMaxShortcutsPerApp;
+    }
+
+    /**
      * - Sends a notification to LauncherApps
      * - Write to file
      */
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8e9a214..4113081 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1577,9 +1577,8 @@
         applyKeyguardPolicy(win, imeTarget);
 
         // Check if the freeform window overlaps with the navigation bar area.
-        final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win);
-        if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar
-                && win.inFreeformWindowingMode()) {
+        if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()
+                && win.mActivityRecord != null && isOverlappingWithNavBar(win)) {
             mIsFreeformWindowOverlappingWithNavBar = true;
         }
 
@@ -1637,7 +1636,7 @@
             // mode; if it's in gesture navigation mode, the navigation bar will be
             // NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping
             // windows.
-            if (isOverlappingWithNavBar) {
+            if (isOverlappingWithNavBar(win)) {
                 if (mNavBarColorWindowCandidate == null) {
                     mNavBarColorWindowCandidate = win;
                     addSystemBarColorApp(win);
@@ -1665,7 +1664,7 @@
                     addSystemBarColorApp(win);
                 }
             }
-            if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
+            if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
             }
         }
@@ -2858,7 +2857,7 @@
 
     @VisibleForTesting
     static boolean isOverlappingWithNavBar(@NonNull WindowState win) {
-        if (win.mActivityRecord == null || !win.isVisible()) {
+        if (!win.isVisible()) {
             return false;
         }