Merge "Temporary fix DialogLaunchAnimatorTest#testShowDialogFromView" into sc-v2-dev
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
index 1c40a06..42fb24f 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
@@ -217,7 +217,7 @@
             }
 
             @Override
-            public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException {
+            public void onTasksAppeared(RemoteAnimationTarget[] app) throws RemoteException {
                 /* no-op */
             }
         };
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 40880c1..e3a6dd0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -113,7 +113,7 @@
     method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopBackgroundUsersOnSwitch(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopUserOnSwitch(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -128,9 +128,9 @@
     field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
-    field public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1; // 0xffffffff
-    field public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0; // 0x0
-    field public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1; // 0x1
+    field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
+    field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0
+    field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1
   }
 
   public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable {
@@ -785,6 +785,7 @@
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f;
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
+    field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
   }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 50a562b..f0deeca 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4081,45 +4081,46 @@
      * @hide
      */
     @TestApi
-    public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1;
+    public static final int STOP_USER_ON_SWITCH_DEFAULT = -1;
 
     /**
-     * Overrides value defined by the platform and stop background users on switch.
+     * Overrides value defined by the platform and stop user on switch.
      *
      * @hide
      */
     @TestApi
-    public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1;
+    public static final int STOP_USER_ON_SWITCH_TRUE = 1;
 
     /**
-     * Overrides value defined by the platform and don't stop background users on switch.
+     * Overrides value defined by the platform and don't stop user on switch.
      *
      * @hide
      */
     @TestApi
-    public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0;
+    public static final int STOP_USER_ON_SWITCH_FALSE = 0;
 
     /** @hide */
-    @IntDef(prefix = { "STOP_BG_USERS_ON_SWITCH_" }, value = {
-            STOP_BG_USERS_ON_SWITCH_DEFAULT,
-            STOP_BG_USERS_ON_SWITCH_TRUE,
-            STOP_BG_USERS_ON_SWITCH_FALSE
+    @IntDef(prefix = { "STOP_USER_ON_SWITCH_" }, value = {
+            STOP_USER_ON_SWITCH_DEFAULT,
+            STOP_USER_ON_SWITCH_TRUE,
+            STOP_USER_ON_SWITCH_FALSE
     })
-    public @interface StopBgUsersOnSwitch {}
+    public @interface StopUserOnSwitch {}
 
     /**
-     * Sets whether background users should be stopped when the current user is switched.
+     * Sets whether the current foreground user (and its profiles) should be stopped after switched
+     * out.
      *
-     * <p>Should only be used on tests.
+     * <p>Should only be used on tests. Doesn't apply to {@link UserHandle#SYSTEM system user}.
      *
      * @hide
      */
     @TestApi
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
-    public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) {
+    public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
         try {
-            getService().setStopBackgroundUsersOnSwitch(value);
+            getService().setStopUserOnSwitch(value);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b416c95..7be4c3e 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import static android.app.ActivityManager.StopBgUsersOnSwitch;
+import static android.app.ActivityManager.StopUserOnSwitch;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -663,9 +663,10 @@
             @Nullable VoiceInteractionManagerProvider provider);
 
     /**
-     * Sets whether background users should be stopped when the current user is switched.
+     * Sets whether the current foreground user (and its profiles) should be stopped after switched
+     * out.
      */
-    public abstract void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value);
+    public abstract void setStopUserOnSwitch(@StopUserOnSwitch int value);
 
     /**
      * Provides the interface to communicate between voice interaction manager service and
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 81f0b44..431755e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,7 +29,6 @@
 import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
 import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
 import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -315,7 +314,7 @@
 
     @UnsupportedAppUsage
     private ContextImpl mSystemContext;
-    private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>();
+    private ContextImpl mSystemUiContext;
 
     @UnsupportedAppUsage
     static volatile IPackageManager sPackageManager;
@@ -2612,26 +2611,22 @@
     }
 
     @Override
-    @NonNull
     public ContextImpl getSystemUiContext() {
-        return getSystemUiContext(DEFAULT_DISPLAY);
+        synchronized (this) {
+            if (mSystemUiContext == null) {
+                mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext());
+            }
+            return mSystemUiContext;
+        }
     }
 
     /**
-     * Gets the context instance base on system resources & display information which used for UI.
+     * Create the context instance base on system resources & display information which used for UI.
      * @param displayId The ID of the display where the UI is shown.
      * @see ContextImpl#createSystemUiContext(ContextImpl, int)
      */
-    @NonNull
-    public ContextImpl getSystemUiContext(int displayId) {
-        synchronized (this) {
-            ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId);
-            if (systemUiContext == null) {
-                systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
-                mDisplaySystemUiContexts.put(displayId, systemUiContext);
-            }
-            return systemUiContext;
-        }
+    public ContextImpl createSystemUiContext(int displayId) {
+        return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId);
     }
 
     public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
@@ -3750,7 +3745,7 @@
         if (pkgName != null && !pkgName.isEmpty()
                 && r.packageInfo.mPackageName.contains(pkgName)) {
             for (int id : dm.getDisplayIds()) {
-                if (id != DEFAULT_DISPLAY) {
+                if (id != Display.DEFAULT_DISPLAY) {
                     Display display =
                             dm.getCompatibleDisplay(id, appContext.getResources());
                     appContext = (ContextImpl) appContext.createDisplayContext(display);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ed496c6..b5ed171 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2618,10 +2618,7 @@
                 overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(),
                 mResources.getLoaders()));
         context.mDisplay = display;
-        // Inherit context type if the container is from System or System UI context to bypass
-        // UI context check.
-        context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
-                ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_DISPLAY_CONTEXT;
+        context.mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT;
         // Display contexts and any context derived from a display context should always override
         // the display that would otherwise be inherited from mToken (or the global configuration if
         // mToken is null).
@@ -2674,8 +2671,7 @@
 
         // Step 2. Create the base context of the window context, it will also create a Resources
         //         associated with the WindowTokenClient and set the token to the base context.
-        final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient,
-                display.getDisplayId());
+        final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display);
 
         // Step 3. Create a WindowContext instance and set it as the outer context of the base
         //         context to make the service obtained by #getSystemService(String) able to query
@@ -2700,7 +2696,9 @@
         if (display == null) {
             throw new IllegalArgumentException("Display must not be null");
         }
-        return createWindowContextBase(token, display.getDisplayId());
+        final ContextImpl tokenContext = createWindowContextBase(token, display);
+        tokenContext.setResources(createWindowContextResources(tokenContext));
+        return tokenContext;
     }
 
     /**
@@ -2708,13 +2706,13 @@
      * window.
      *
      * @param token The token to associate with {@link Resources}
-     * @param displayId The ID of {@link Display} to associate with.
+     * @param display The {@link Display} to associate with.
      *
      * @see #createWindowContext(Display, int, Bundle)
      * @see #createTokenContext(IBinder, Display)
      */
     @UiContext
-    ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) {
+    ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) {
         ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
                 mAttributionSource.getAttributionTag(),
                 mAttributionSource.getNext(),
@@ -2728,8 +2726,8 @@
         baseContext.setResources(windowContextResources);
         // Associate the display with window context resources so that configuration update from
         // the server side will also apply to the display's metrics.
-        baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
-                windowContextResources);
+        baseContext.mDisplay = ResourcesManager.getInstance()
+                .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
 
         return baseContext;
     }
@@ -2965,16 +2963,6 @@
         mContentCaptureOptions = options;
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        // If token is a WindowTokenClient, the Context is usually associated with a
-        // WindowContainer. We should detach from WindowContainer when the Context is finalized.
-        if (mToken instanceof WindowTokenClient) {
-            ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
-        }
-        super.finalize();
-    }
-
     @UnsupportedAppUsage
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
@@ -2995,15 +2983,24 @@
      * @param displayId The ID of the display where the UI is shown.
      */
     static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) {
-        final WindowTokenClient token = new WindowTokenClient();
-        ContextImpl context = systemContext.createWindowContextBase(token, displayId);
-        token.attachContext(context);
-        token.attachToDisplayContent(displayId);
+        final LoadedApk packageInfo = systemContext.mPackageInfo;
+        ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo,
+                ContextParams.EMPTY, null, null, null, null, null, 0, null, null);
+        context.setResources(createResources(null, packageInfo, null, displayId, null,
+                packageInfo.getCompatibilityInfo(), null));
+        context.updateDisplay(displayId);
         context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
-
         return context;
     }
 
+    /**
+     * The overloaded method of {@link #createSystemUiContext(ContextImpl, int)}.
+     * Uses {@Code Display.DEFAULT_DISPLAY} as the target display.
+     */
+    static ContextImpl createSystemUiContext(ContextImpl systemContext) {
+        return createSystemUiContext(systemContext, Display.DEFAULT_DISPLAY);
+    }
+
     @UnsupportedAppUsage
     static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
         return createAppContext(mainThread, packageInfo, null);
@@ -3209,13 +3206,7 @@
 
     @Override
     public IBinder getWindowContextToken() {
-        switch (mContextType) {
-            case CONTEXT_TYPE_WINDOW_CONTEXT:
-            case CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI:
-                return mToken;
-            default:
-                return null;
-        }
+        return mContextType == CONTEXT_TYPE_WINDOW_CONTEXT ? mToken : null;
     }
 
     private void checkMode(int mode) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 601ec9a..0210a94 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -341,7 +341,7 @@
     @UnsupportedAppUsage
     boolean switchUser(int userid);
     @UnsupportedAppUsage
-    void setStopBackgroundUsersOnSwitch(int value);
+    void setStopUserOnSwitch(int value);
     boolean removeTask(int taskId);
     @UnsupportedAppUsage
     void registerProcessObserver(in IProcessObserver observer);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 11c01e6..22b1c03 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -240,6 +240,14 @@
      */
     public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID";
 
+    /**
+     * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from
+     * a foreground app.
+     * @hide
+     */
+    public static final String EXTRA_FROM_FOREGROUND_APP =
+            "android.service.wallpaper.extra.FROM_FOREGROUND_APP";
+
     // flags for which kind of wallpaper to act on
 
     /** @hide */
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index a0d2977..01875ed 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.app.admin;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
@@ -77,6 +78,13 @@
             OnCrossProfileWidgetProvidersChangeListener listener);
 
     /**
+     * @param userHandle the handle of the user whose profile owner is being fetched.
+     * @return the configured supervision app if it exists and is the device owner or policy owner.
+     */
+    public abstract @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+            @NonNull UserHandle userHandle);
+
+    /**
      * Checks if an app with given uid is an active device owner of its user.
      *
      * <p>This takes the DPMS lock.  DO NOT call from PM/UM/AM with their lock held.
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a02fdd3..b5298fc 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -995,10 +995,9 @@
      * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
      * OVERRIDE_MIN_ASPECT_RATIO_LARGE
      *
-     * If OVERRIDE_MIN_ASPECT_RATIO is applied, and the activity's orientation is fixed to
-     * portrait, the min aspect ratio given in the app's manifest will be overridden to the
-     * largest enabled aspect ratio treatment unless the app's manifest value is higher.
-     * TODO(b/203647190): add OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY instead of portrait by default
+     * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest
+     * will be overridden to the largest enabled aspect ratio treatment unless the app's manifest
+     * value is higher.
      * @hide
      */
     @ChangeId
@@ -1008,6 +1007,19 @@
     public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
 
     /**
+     * This change id restricts treatments that force a given min aspect ratio to activities
+     * whose orientation is fixed to portrait.
+     *
+     * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2)
+    @TestApi
+    public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id
+
+    /**
      * This change id sets the activity's min aspect ratio to a medium value as defined by
      * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE.
      *
@@ -1336,9 +1348,7 @@
      */
     @SizeChangesSupportMode
     public int supportsSizeChanges() {
-        if (CompatChanges.isChangeEnabled(FORCE_NON_RESIZE_APP,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+        if (isChangeEnabled(FORCE_NON_RESIZE_APP)) {
             return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
         }
 
@@ -1346,9 +1356,7 @@
             return SIZE_CHANGES_SUPPORTED_METADATA;
         }
 
-        if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+        if (isChangeEnabled(FORCE_RESIZE_APP)) {
             return SIZE_CHANGES_SUPPORTED_OVERRIDE;
         }
 
@@ -1360,9 +1368,7 @@
      * @hide
      */
     public boolean neverSandboxDisplayApis() {
-        return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))
+        return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS)
                 || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo);
     }
 
@@ -1371,9 +1377,7 @@
      * @hide
      */
     public boolean alwaysSandboxDisplayApis() {
-        return CompatChanges.isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))
+        return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS)
                 || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo);
     }
 
@@ -1403,31 +1407,28 @@
      * @hide
      */
     public float getMinAspectRatio(@ScreenOrientation int orientation) {
-        // TODO(b/203647190): check orientation only if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY
-        // In case the activity's orientation isn't fixed to portrait, OVERRIDE_MIN_ASPECT_RATIO
-        // shouldn't be applied.
-        if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))
-                || !isFixedOrientationPortrait(orientation)) {
+        if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
+                isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+                        && !isFixedOrientationPortrait(orientation))) {
             return mMinAspectRatio;
         }
 
-        if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+        if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
             return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio);
         }
 
-        if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+        if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
             return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio);
         }
 
         return mMinAspectRatio;
     }
 
+    private boolean isChangeEnabled(long changeId) {
+        return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
+                UserHandle.getUserHandleForUid(applicationInfo.uid));
+    }
+
     /** @hide */
     public float getManifestMinAspectRatio() {
         return mMinAspectRatio;
@@ -1495,9 +1496,7 @@
      * @hide
      */
     public boolean shouldCheckMinWidthHeightForMultiWindow() {
-        return CompatChanges.isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW,
-                applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid));
+        return isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW);
     }
 
     public void dump(Printer pw, String prefix) {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c8c122d..6b5bec9 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -408,6 +408,19 @@
         }
 
         /**
+         * Flag to decide if authentication should ignore enrollment state.
+         * Defaults to false (not ignoring enrollment state)
+         * @param ignoreEnrollmentState
+         * @return This builder.
+         * @hide
+         */
+        @NonNull
+        public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
+            mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          *
          * @return An instance of {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 339c654..e6b762a 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -45,6 +45,7 @@
     private boolean mReceiveSystemEvents;
     @NonNull private List<Integer> mAllowedSensorIds = new ArrayList<>();
     private boolean mAllowBackgroundAuthentication;
+    private boolean mIgnoreEnrollmentState;
 
     public PromptInfo() {
 
@@ -66,6 +67,7 @@
         mReceiveSystemEvents = in.readBoolean();
         mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader());
         mAllowBackgroundAuthentication = in.readBoolean();
+        mIgnoreEnrollmentState = in.readBoolean();
     }
 
     public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -102,6 +104,7 @@
         dest.writeBoolean(mReceiveSystemEvents);
         dest.writeList(mAllowedSensorIds);
         dest.writeBoolean(mAllowBackgroundAuthentication);
+        dest.writeBoolean(mIgnoreEnrollmentState);
     }
 
     public boolean containsTestConfigurations() {
@@ -192,6 +195,10 @@
         mAllowBackgroundAuthentication = allow;
     }
 
+    public void setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
+        mIgnoreEnrollmentState = ignoreEnrollmentState;
+    }
+
     // Getters
 
     public CharSequence getTitle() {
@@ -261,4 +268,8 @@
     public boolean isAllowBackgroundAuthentication() {
         return mAllowBackgroundAuthentication;
     }
+
+    public boolean isIgnoreEnrollmentState() {
+        return mIgnoreEnrollmentState;
+    }
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index a3d595c..fe04e5d 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -531,7 +531,7 @@
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
-        authenticate(crypto, cancel, callback, handler, mContext.getUserId());
+        authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, mContext.getUserId(), flags);
     }
 
     /**
@@ -541,7 +541,7 @@
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             @NonNull AuthenticationCallback callback, Handler handler, int userId) {
-        authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId);
+        authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId, 0 /* flags */);
     }
 
     /**
@@ -550,7 +550,8 @@
      */
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId) {
+            @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId,
+            int flags) {
 
         FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED,
                 AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE,
@@ -566,6 +567,8 @@
             return;
         }
 
+        final boolean ignoreEnrollmentState = flags == 0 ? false : true;
+
         if (mService != null) {
             try {
                 useHandler(handler);
@@ -573,7 +576,7 @@
                 mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 final long authId = mService.authenticate(mToken, operationId, sensorId, userId,
-                        mServiceReceiver, mContext.getOpPackageName());
+                        mServiceReceiver, mContext.getOpPackageName(), ignoreEnrollmentState);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index de94b2f..ba1dc6d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -52,7 +52,8 @@
     // permission. This is effectively deprecated, since it only comes through FingerprintManager
     // now. A requestId is returned that can be used to cancel this operation.
     long authenticate(IBinder token, long operationId, int sensorId, int userId,
-            IFingerprintServiceReceiver receiver, String opPackageName);
+            IFingerprintServiceReceiver receiver, String opPackageName,
+            boolean shouldIgnoreEnrollmentState);
 
     // Uses the fingerprint hardware to detect for the presence of a finger, without giving details
     // about accept/reject/lockout. A requestId is returned that can be used to cancel this
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 2d263a5..c77399f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1575,7 +1575,7 @@
         void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
                 float xOffsetStep) {
             // to save creating a runnable, check twice
-            long current = SystemClock.elapsedRealtime();
+            long current = System.currentTimeMillis();
             long lapsed = current - currentPage.getLastUpdateTime();
             // Always update the page when the last update time is <= 0
             // This is important especially when the device first boots
@@ -1768,6 +1768,7 @@
                     return;
                 }
             }
+            processLocalColors(mPendingXOffset, mPendingYOffset);
         }
 
         /**
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index aca17e4..c7fd380 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -63,5 +63,5 @@
      * Called when the task of an activity that has been started while the recents animation
      * was running becomes ready for control.
      */
-    void onTaskAppeared(in RemoteAnimationTarget app) = 3;
+    void onTasksAppeared(in RemoteAnimationTarget[] app) = 3;
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index ae32a48..9e41e4d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -866,23 +866,6 @@
     void attachWindowContextToWindowToken(IBinder clientToken, IBinder token);
 
     /**
-     * Attaches a {@code clientToken} to associate with DisplayContent.
-     * <p>
-     * Note that this API should be invoked after calling
-     * {@link android.window.WindowTokenClient#attachContext(Context)}
-     * </p>
-     *
-     * @param clientToken {@link android.window.WindowContext#getWindowContextToken()
-     * the WindowContext's token}
-     * @param displayId The display associated with the window context
-     *
-     * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is
-     * attached to the DisplayContent successfully. {@code null}, otherwise.
-     * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid
-     */
-    Configuration attachToDisplayContent(IBinder clientToken, int displayId);
-
-    /**
      * Detaches {@link android.window.WindowContext} from the window manager node it's currently
      * attached to. It is no-op if the WindowContext is not attached to a window manager node.
      *
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 51cd95e..8764ccc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2441,6 +2441,20 @@
         public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
 
         /**
+         * Flag to indicate that this window will be excluded while computing the magnifiable region
+         * on the un-scaled screen coordinate, which could avoid the cutout on the magnification
+         * border. It should be used for unmagnifiable overlays.
+         *
+         * </p><p>
+         * Note unlike {@link #PRIVATE_FLAG_NOT_MAGNIFIABLE}, this flag doesn't affect the ability
+         * of magnification. If you want to the window to be unmagnifiable and doesn't lead to the
+         * cutout, you need to combine both of them.
+         * </p><p>
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000;
+
+        /**
          * Flag to prevent the window from being magnified by the accessibility magnifier.
          *
          * TODO(b/190623172): This is a temporary solution and need to find out another way instead.
@@ -2551,6 +2565,7 @@
                 PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                 PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
@@ -2632,6 +2647,10 @@
                         equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
                         name = "IS_ROUNDED_CORNERS_OVERLAY"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
+                        equals = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
+                        name = "EXCLUDE_FROM_SCREEN_MAGNIFICATION"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_NOT_MAGNIFIABLE,
                         equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
                         name = "NOT_MAGNIFIABLE"),
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2357d13..f724285 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5735,24 +5735,44 @@
         return previousLayoutId == getLayoutId() && mViewId == overrideId;
     }
 
-    // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls
-    // should set it to false.
-    private void reapply(Context context, View v, InteractionHandler handler, SizeF size,
-            ColorResources colorResources, boolean topLevel) {
-
+    /**
+     * Returns the RemoteViews that should be used in the reapply operation.
+     *
+     * If the current RemoteViews has multiple layout, this will select the correct one.
+     *
+     * @throws RuntimeException If the current RemoteViews should not be reapplied onto the provided
+     * View.
+     */
+    private RemoteViews getRemoteViewsToReapply(Context context, View v, @Nullable SizeF size) {
         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         // In the case that a view has this RemoteViews applied in one orientation or size, is
         // persisted across change, and has the RemoteViews re-applied in a different situation
         // (orientation or size), we throw an exception, since the layouts may be completely
         // unrelated.
-        if (hasMultipleLayouts()) {
+        // If the ViewID has been changed on the view, or is changed by the RemoteViews, we also
+        // may throw an exception, as the RemoteViews will probably not apply properly.
+        // However, we need to let potentially unrelated RemoteViews apply, as this lack of testing
+        // is already used in production code in some apps.
+        if (hasMultipleLayouts()
+                || rvToApply.mViewId != View.NO_ID
+                || v.getTag(R.id.remote_views_override_id) != null) {
             if (!rvToApply.canRecycleView(v)) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
             }
         }
 
+        return rvToApply;
+    }
+
+    // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls
+    // should set it to false.
+    private void reapply(Context context, View v, InteractionHandler handler, SizeF size,
+            ColorResources colorResources, boolean topLevel) {
+
+        RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+
         rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources);
 
         // If the parent of the view is has is a root, resolve the recycling.
@@ -5789,17 +5809,7 @@
     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
             OnViewAppliedListener listener, InteractionHandler handler, SizeF size,
             ColorResources colorResources) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
-
-        // In the case that a view has this RemoteViews applied in one orientation, is persisted
-        // across orientation change, and has the RemoteViews re-applied in the new orientation,
-        // we throw an exception, since the layouts may be completely unrelated.
-        if (hasMultipleLayouts()) {
-            if (!rvToApply.canRecycleView(v)) {
-                throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
-                        " that does not share the same root layout id.");
-            }
-        }
+        RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
 
         return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
                 context, listener, handler, colorResources, v, true /* topLevel */)
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 17b675f..5aa6233 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,10 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.view.IWindowManager;
 import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -35,6 +38,7 @@
  * @hide
  */
 public class WindowContextController {
+    private final IWindowManager mWms;
     /**
      * {@code true} to indicate that the {@code mToken} is associated with a
      * {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a
@@ -52,7 +56,14 @@
      *              {@link Context#getWindowContextToken()}.
      */
     public WindowContextController(@NonNull WindowTokenClient token) {
+        this(token, WindowManagerGlobal.getWindowManagerService());
+    }
+
+    /** Used for test only. DO NOT USE it in production code. */
+    @VisibleForTesting
+    public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
         mToken = token;
+        mWms = mockWms;
     }
 
     /**
@@ -69,7 +80,19 @@
             throw new IllegalStateException("A Window Context can be only attached to "
                     + "a DisplayArea once.");
         }
-        mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options);
+        try {
+            final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
+                    displayId, options);
+            if (configuration != null) {
+                mAttachedToDisplayArea = true;
+                // Send the DisplayArea's configuration to WindowContext directly instead of
+                // waiting for dispatching from WMS.
+                mToken.onConfigurationChanged(configuration, displayId,
+                        false /* shouldReportConfigChange */);
+            }
+        }  catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -97,14 +120,22 @@
             throw new IllegalStateException("The Window Context should have been attached"
                     + " to a DisplayArea.");
         }
-        mToken.attachToWindowToken(windowToken);
+        try {
+            mWms.attachWindowContextToWindowToken(mToken, windowToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** Detaches the window context from the node it's currently associated with. */
     public void detachIfNeeded() {
         if (mAttachedToDisplayArea) {
-            mToken.detachFromWindowContainerIfNeeded();
-            mAttachedToDisplayArea = false;
+            try {
+                mWms.detachWindowContextFromWindowContainer(mToken);
+                mAttachedToDisplayArea = false;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 }
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index b331a9e..f3e3859 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -21,7 +21,6 @@
 import static android.window.ConfigurationHelper.shouldUpdateResources;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.IWindowToken;
 import android.app.ResourcesManager;
@@ -32,11 +31,7 @@
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.util.Log;
-import android.view.IWindowManager;
-import android.view.WindowManager.LayoutParams.WindowType;
-import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -64,14 +59,10 @@
 
     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
-    private IWindowManager mWms;
-
     private final Configuration mConfiguration = new Configuration();
 
     private boolean mShouldDumpConfigForIme;
 
-    private boolean mAttachToWindowContainer;
-
     /**
      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
      * can only attach one {@link Context}.
@@ -93,88 +84,6 @@
     }
 
     /**
-     * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
-     *
-     * @param type The window type of the {@link WindowContext}
-     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
-     * @param options The window context launched option
-     * @return {@code true} if attaching successfully.
-     */
-    public boolean attachToDisplayArea(@WindowType int type, int displayId,
-            @Nullable Bundle options) {
-        try {
-            final Configuration configuration = getWindowManagerService()
-                    .attachWindowContextToDisplayArea(this, type, displayId, options);
-            if (configuration == null) {
-                return false;
-            }
-            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
-            mAttachToWindowContainer = true;
-            return true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}.
-     *
-     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
-     * @return {@code true} if attaching successfully.
-     */
-    public boolean attachToDisplayContent(int displayId) {
-        final IWindowManager wms = getWindowManagerService();
-        // #createSystemUiContext may call this method before WindowManagerService is initialized.
-        if (wms == null) {
-            return false;
-        }
-        try {
-            final Configuration configuration = wms.attachToDisplayContent(this, displayId);
-            if (configuration == null) {
-                return false;
-            }
-            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
-            mAttachToWindowContainer = true;
-            return true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
-     *
-     * @param windowToken the window token to associated with
-     */
-    public void attachToWindowToken(IBinder windowToken) {
-        try {
-            getWindowManagerService().attachWindowContextToWindowToken(this, windowToken);
-            mAttachToWindowContainer = true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */
-    public void detachFromWindowContainerIfNeeded() {
-        if (!mAttachToWindowContainer) {
-            return;
-        }
-        try {
-            getWindowManagerService().detachWindowContextFromWindowContainer(this);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private IWindowManager getWindowManagerService() {
-        if (mWms == null) {
-            mWms = WindowManagerGlobal.getWindowManagerService();
-        }
-        return mWms;
-    }
-
-    /**
      * Called when {@link Configuration} updates from the server side receive.
      *
      * @param newConfig the updated {@link Configuration}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 32db186..90646a8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1930,8 +1930,9 @@
          STREAM_MUSIC as if it's on TV platform. -->
     <bool name="config_single_volume">false</bool>
 
-    <!-- Flag indicating whether the volume panel should show remote sessions. -->
-    <bool name="config_volumeShowRemoteSessions">true</bool>
+    <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
+         on grouped devices. -->
+    <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
 
     <!-- Flag indicating that an outbound call must have a call capable phone account
          that has declared it can process the call's handle. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 758990d..9bb92e6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4444,7 +4444,7 @@
 
   <java-symbol type="dimen" name="config_wallpaperDimAmount" />
 
-  <java-symbol type="bool" name="config_volumeShowRemoteSessions" />
+  <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
 
   <java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
 
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 52cb9f3..a6e351d 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -24,13 +24,16 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -56,14 +59,17 @@
 public class WindowContextControllerTest {
     private WindowContextController mController;
     @Mock
+    private IWindowManager mMockWms;
+    @Mock
     private WindowTokenClient mMockToken;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mController = new WindowContextController(mMockToken);
+        mController = new WindowContextController(mMockToken, mMockWms);
         doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
-        doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any());
+        doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
+                anyInt(), anyInt(), any());
     }
 
     @Test(expected = IllegalStateException.class)
@@ -75,10 +81,10 @@
     }
 
     @Test
-    public void testDetachIfNeeded_NotAttachedYet_DoNothing() {
+    public void testDetachIfNeeded_NotAttachedYet_DoNothing() throws Exception {
         mController.detachIfNeeded();
 
-        verify(mMockToken, never()).detachFromWindowContainerIfNeeded();
+        verify(mMockWms, never()).detachWindowContextFromWindowContainer(any());
     }
 
     @Test
@@ -87,6 +93,8 @@
                 null /* options */);
 
         assertThat(mController.mAttachedToDisplayArea).isTrue();
+        verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY),
+                eq(false) /* shouldReportConfigChange */);
 
         mController.detachIfNeeded();
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 1e9fda6..44af1a9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -73,17 +73,55 @@
     static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
         final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
         final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
-                && ((SplitPairRule) splitRule).shouldFinishPrimaryWithSecondary();
+                && ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary()
+                != SplitRule.FINISH_NEVER;
         return shouldFinishPrimaryWithSecondary || isPlaceholderContainer;
     }
 
     static boolean shouldFinishSecondaryWithPrimary(@NonNull SplitRule splitRule) {
         final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
         final boolean shouldFinishSecondaryWithPrimary = (splitRule instanceof SplitPairRule)
-                && ((SplitPairRule) splitRule).shouldFinishSecondaryWithPrimary();
+                && ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary()
+                != SplitRule.FINISH_NEVER;
         return shouldFinishSecondaryWithPrimary || isPlaceholderContainer;
     }
 
+    static boolean shouldFinishAssociatedContainerWhenStacked(int finishBehavior) {
+        return finishBehavior == SplitRule.FINISH_ALWAYS;
+    }
+
+    static boolean shouldFinishAssociatedContainerWhenAdjacent(int finishBehavior) {
+        return finishBehavior == SplitRule.FINISH_ALWAYS
+                || finishBehavior == SplitRule.FINISH_ADJACENT;
+    }
+
+    static int getFinishPrimaryWithSecondaryBehavior(@NonNull SplitRule splitRule) {
+        if (splitRule instanceof SplitPlaceholderRule) {
+            return ((SplitPlaceholderRule) splitRule).getFinishPrimaryWithSecondary();
+        }
+        if (splitRule instanceof SplitPairRule) {
+            return ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary();
+        }
+        return SplitRule.FINISH_NEVER;
+    }
+
+    static int getFinishSecondaryWithPrimaryBehavior(@NonNull SplitRule splitRule) {
+        if (splitRule instanceof SplitPlaceholderRule) {
+            return SplitRule.FINISH_ALWAYS;
+        }
+        if (splitRule instanceof SplitPairRule) {
+            return ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary();
+        }
+        return SplitRule.FINISH_NEVER;
+    }
+
+    static boolean isStickyPlaceholderRule(@NonNull SplitRule splitRule) {
+        if (!(splitRule instanceof SplitPlaceholderRule)) {
+            return false;
+        }
+        return ((SplitPlaceholderRule) splitRule).isSticky();
+    }
+
     @Override
     public String toString() {
         return "SplitContainer{"
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 9014102..68c1904 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -16,6 +16,12 @@
 
 package androidx.window.extensions.embedding;
 
+import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -460,6 +466,11 @@
             return false;
         }
 
+        if (isStickyPlaceholderRule(splitContainer.getSplitRule())) {
+            // The placeholder should remain after it was first shown.
+            return false;
+        }
+
         if (mPresenter.shouldShowSideBySide(splitContainer)) {
             return false;
         }
@@ -643,6 +654,52 @@
         return false;
     }
 
+    /**
+     * Checks whether the associated container should be destroyed together with a finishing
+     * container. There is a case when primary containers for placeholders should be retained
+     * despite the rule configuration to finish primary with secondary - if they are marked as
+     * 'sticky' and the placeholder was finished when fully overlapping the primary container.
+     * @return {@code true} if the associated container should be retained (and not be finished).
+     */
+    boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
+            @NonNull TaskFragmentContainer associatedContainer) {
+        SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
+                finishingContainer);
+        if (splitContainer == null) {
+            // Containers are not in the same split, no need to retain.
+            return false;
+        }
+        // Find the finish behavior for the associated container
+        int finishBehavior;
+        SplitRule splitRule = splitContainer.getSplitRule();
+        if (finishingContainer == splitContainer.getPrimaryContainer()) {
+            finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule);
+        } else {
+            finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule);
+        }
+        // Decide whether the associated container should be retained based on the current
+        // presentation mode.
+        if (mPresenter.shouldShowSideBySide(splitContainer)) {
+            return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
+        } else {
+            return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
+        }
+    }
+
+    /**
+     * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer)
+     */
+    boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer,
+            @NonNull Activity associatedActivity) {
+        TaskFragmentContainer associatedContainer = getContainerWithActivity(
+                associatedActivity.getActivityToken());
+        if (associatedContainer == null) {
+            return false;
+        }
+
+        return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
+    }
+
     private final class LifecycleCallbacks implements ActivityLifecycleCallbacks {
 
         @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 6805fde..a1a53bc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -228,6 +228,9 @@
 
         // Finish dependent containers
         for (TaskFragmentContainer container : mContainersToFinishOnExit) {
+            if (controller.shouldRetainAssociatedContainer(this, container)) {
+                continue;
+            }
             container.finish(true /* shouldFinishDependent */, presenter,
                     wct, controller);
         }
@@ -235,6 +238,9 @@
 
         // Finish associated activities
         for (Activity activity : mActivitiesToFinishOnExit) {
+            if (controller.shouldRetainAssociatedActivity(this, activity)) {
+                continue;
+            }
             activity.finish();
         }
         mActivitiesToFinishOnExit.clear();
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 830d13d..d6678bf 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
index 9fe0247..7dc2f31 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml
@@ -65,25 +65,28 @@
     <LinearLayout
         android:id="@+id/top_end_container"
         android:layout_gravity="top|end"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal">
+
         <ImageButton
             android:id="@+id/settings"
             android:layout_width="@dimen/pip_action_size"
             android:layout_height="@dimen/pip_action_size"
             android:contentDescription="@string/pip_phone_settings"
+            android:layout_gravity="top|start"
             android:gravity="center"
             android:src="@drawable/pip_ic_settings"
             android:background="?android:selectableItemBackgroundBorderless" />
 
         <ImageButton
-            android:id="@+id/dismiss"
+            android:id="@+id/enter_split"
             android:layout_width="@dimen/pip_action_size"
             android:layout_height="@dimen/pip_action_size"
-            android:contentDescription="@string/pip_phone_close"
+            android:layout_gravity="top|start"
             android:gravity="center"
-            android:src="@drawable/pip_ic_close_white"
+            android:contentDescription="@string/pip_phone_enter_split"
+            android:src="@drawable/pip_expand"
             android:background="?android:selectableItemBackgroundBorderless" />
     </LinearLayout>
 
@@ -97,4 +100,14 @@
         android:padding="@dimen/pip_resize_handle_padding"
         android:src="@drawable/pip_resize_handle"
         android:background="?android:selectableItemBackgroundBorderless" />
+
+    <ImageButton
+        android:id="@+id/dismiss"
+        android:layout_width="@dimen/pip_action_size"
+        android:layout_height="@dimen/pip_action_size"
+        android:contentDescription="@string/pip_phone_close"
+        android:layout_gravity="top|end"
+        android:gravity="center"
+        android:src="@drawable/pip_ic_close_white"
+        android:background="?android:selectableItemBackgroundBorderless" />
 </FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 764854a..c88fc16 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -24,6 +24,9 @@
     <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
     <string name="pip_phone_settings">Settings</string>
 
+    <!-- Label for the PIP enter split button [CHAR LIMIT=NONE] -->
+    <string name="pip_phone_enter_split">Enter split screen</string>
+
     <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
     <string name="pip_menu_title">Menu</string>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index f7af4e1..caa5327 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1036,10 +1036,9 @@
                 // notification, so that the bubble will be re-created if shouldBubbleUp returns
                 // true.
                 mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
-            } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble
-                    && !entry.getRanking().isSuspended()) {
+            } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
                 entry.setFlagBubble(true);
-                onEntryUpdated(entry, true /* shouldBubbleUp */);
+                onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended());
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index b80dcd0..711a0ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.pip.tv.TvPipMenuController;
 import com.android.wm.shell.pip.tv.TvPipNotificationController;
 import com.android.wm.shell.pip.tv.TvPipTransition;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
@@ -160,13 +161,14 @@
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<LegacySplitScreenController> splitScreenOptional,
+            Optional<SplitScreenController> newSplitScreenOptional,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
                 syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
                 tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
-                pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
-                shellTaskOrganizer, mainExecutor);
+                pipTransitionController, splitScreenOptional, newSplitScreenOptional,
+                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
     }
 }
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 944dfed..ec70147 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
@@ -55,6 +55,7 @@
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
 import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
 import com.android.wm.shell.transition.Transitions;
@@ -215,14 +216,15 @@
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             PipTransitionController pipTransitionController,
             Optional<LegacySplitScreenController> splitScreenOptional,
+            Optional<SplitScreenController> newSplitScreenOptional,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
                 syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
                 menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
-                pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
-                shellTaskOrganizer, mainExecutor);
+                pipTransitionController, splitScreenOptional, newSplitScreenOptional,
+                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b6e5804..6cc5f09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -77,6 +77,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
@@ -126,7 +127,8 @@
     private final int mExitAnimationDuration;
     private final int mCrossFadeAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Optional<LegacySplitScreenController> mSplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
+    private final Optional<SplitScreenController> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -252,7 +254,8 @@
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @NonNull PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreenController> splitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -274,6 +277,7 @@
         mPipAnimationController = pipAnimationController;
         mPipUiEventLoggerLogger = pipUiEventLogger;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+        mLegacySplitScreenOptional = legacySplitScreenOptional;
         mSplitScreenOptional = splitScreenOptional;
         mTaskOrganizer = shellTaskOrganizer;
         mMainExecutor = mainExecutor;
@@ -373,8 +377,11 @@
      *   activity render it's final configuration while the Task is still in PiP.
      * - setWindowingMode to undefined at the end of transition
      * @param animationDurationMs duration in millisecond for the exiting PiP transition
+     * @param requestEnterSplit whether the enterSplit button is pressed on PiP or not.
+     *                             Indicate the user wishes to directly put PiP into split screen
+     *                             mode.
      */
-    public void exitPip(int animationDurationMs) {
+    public void exitPip(int animationDurationMs, boolean requestEnterSplit) {
         if (!mPipTransitionState.isInPip()
                 || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
                 || mToken == null) {
@@ -387,7 +394,7 @@
                 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final Rect destinationBounds = mPipBoundsState.getDisplayBounds();
-        final int direction = syncWithSplitScreenBounds(destinationBounds)
+        final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
                 : TRANSITION_DIRECTION_LEAVE_PIP;
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
@@ -396,7 +403,7 @@
         // We set to fullscreen here for now, but later it will be set to UNDEFINED for
         // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
         wct.setActivityWindowingMode(mToken,
-                direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
+                direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit
                         ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         : WINDOWING_MODE_FULLSCREEN);
         wct.setBounds(mToken, destinationBounds);
@@ -435,7 +442,7 @@
         wct.setWindowingMode(mToken, getOutPipWindowingMode());
         // Simply reset the activity mode set prior to the animation running.
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-        mSplitScreenOptional.ifPresent(splitScreen -> {
+        mLegacySplitScreenOptional.ifPresent(splitScreen -> {
             if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
                 wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
             }
@@ -1165,6 +1172,7 @@
             @PipAnimationController.TransitionDirection int direction,
             @PipAnimationController.AnimationType int type) {
         final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
+        final boolean isPipTopLeft = isPipTopLeft();
         mPipBoundsState.setBounds(destinationBounds);
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
@@ -1210,10 +1218,10 @@
                             null /* callback */, false /* withStartDelay */);
                 });
             } else {
-                applyFinishBoundsResize(wct, direction);
+                applyFinishBoundsResize(wct, direction, isPipTopLeft);
             }
         } else {
-            applyFinishBoundsResize(wct, direction);
+            applyFinishBoundsResize(wct, direction, isPipTopLeft);
         }
 
         finishResizeForMenu(destinationBounds);
@@ -1241,7 +1249,11 @@
         } else if (isOutPipDirection(direction)) {
             // If we are animating to fullscreen or split screen, then we need to reset the
             // override bounds on the task to ensure that the task "matches" the parent's bounds.
-            taskBounds = null;
+            if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+                taskBounds = destinationBounds;
+            } else {
+                taskBounds = null;
+            }
             applyWindowingModeChangeOnExit(wct, direction);
         } else {
             // Just a resize in PIP
@@ -1261,8 +1273,20 @@
      * applying it.
      */
     public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
-            @PipAnimationController.TransitionDirection int direction) {
-        mTaskOrganizer.applyTransaction(wct);
+            @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) {
+        if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+            mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct);
+        } else {
+            mTaskOrganizer.applyTransaction(wct);
+        }
+    }
+
+    private boolean isPipTopLeft() {
+        final Rect topLeft = new Rect();
+        final Rect bottomRight = new Rect();
+        mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
+
+        return topLeft.contains(mPipBoundsState.getBounds());
     }
 
     /**
@@ -1347,18 +1371,27 @@
     }
 
     /**
-     * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
-     * screen.
+     * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination
+     * bounds if PiP is going to split screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
      */
-    private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) {
-        if (!mSplitScreenOptional.isPresent()) {
+    private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
+        if (enterSplit && mSplitScreenOptional.isPresent()) {
+            final Rect topLeft = new Rect();
+            final Rect bottomRight = new Rect();
+            mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
+            final boolean isPipTopLeft = isPipTopLeft();
+            destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
+            return true;
+        }
+
+        if (!mLegacySplitScreenOptional.isPresent()) {
             return false;
         }
 
-        LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
+        LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get();
         if (!legacySplitScreen.isDividerVisible()) {
             // fail early if system is not in split screen mode
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index ae8c1b6..5687f4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -95,6 +95,11 @@
          * Called when the PIP requested to show the menu.
          */
         void onPipShowMenu();
+
+        /**
+         * Called when the PIP requested to enter Split.
+         */
+        void onEnterSplit();
     }
 
     private final Matrix mMoveTransform = new Matrix();
@@ -458,6 +463,10 @@
         mListeners.forEach(Listener::onPipDismiss);
     }
 
+    void onEnterSplit() {
+        mListeners.forEach(Listener::onEnterSplit);
+    }
+
     /**
      * @return the best set of actions to show in the PiP menu.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 47a8c67..69ae45d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -151,7 +151,7 @@
                         result = true;
                         break;
                     case AccessibilityNodeInfo.ACTION_EXPAND:
-                        mMotionHelper.expandLeavePip();
+                        mMotionHelper.expandLeavePip(false /* skipAnimation */);
                         result = true;
                         break;
                     default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 8c431f0..10bc7e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -482,7 +482,8 @@
                     false /* fromShelfAdjustment */,
                     wct /* windowContainerTransaction */);
             if (wct != null) {
-                mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME);
+                mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME,
+                        false /* wasPipTopLeft */);
             }
         };
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
index 3eeba6e..0644657 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
@@ -18,8 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.util.Log;
-import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -34,6 +32,7 @@
     protected ViewGroup mViewRoot;
     protected ViewGroup mTopEndContainer;
     protected View mDragHandle;
+    protected View mEnterSplitButton;
     protected View mSettingsButton;
     protected View mDismissButton;
 
@@ -44,14 +43,13 @@
      * Bind the necessary views.
      */
     public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
-            View settingsButton, View dismissButton) {
+            View enterSplitButton, View settingsButton, View dismissButton) {
         mViewRoot = viewRoot;
         mTopEndContainer = topEndContainer;
         mDragHandle = dragHandle;
+        mEnterSplitButton = enterSplitButton;
         mSettingsButton = settingsButton;
         mDismissButton = dismissButton;
-
-        bindInitialViewState();
     }
 
     /**
@@ -72,22 +70,4 @@
             v.setLayoutParams(params);
         }
     }
-
-    /** Calculate the initial state of the menu icons. Called when the menu is first created. */
-    private void bindInitialViewState() {
-        if (mViewRoot == null || mTopEndContainer == null || mDragHandle == null
-                || mSettingsButton == null || mDismissButton == null) {
-            Log.e(TAG, "One of the required views is null.");
-            return;
-        }
-        // The menu view layout starts out with the settings button aligned at the top|end of the
-        // view group next to the dismiss button. On phones, the settings button should be aligned
-        // to the top|start of the view, so move it to parent view group to then align it to the
-        // top|start of the menu.
-        mTopEndContainer.removeView(mSettingsButton);
-        mViewRoot.addView(mSettingsButton);
-
-        setLayoutGravity(mDragHandle, Gravity.START | Gravity.TOP);
-        setLayoutGravity(mSettingsButton, Gravity.START | Gravity.TOP);
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 8ef2b6b..7bbebe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -99,7 +99,7 @@
     private static final float MENU_BACKGROUND_ALPHA = 0.3f;
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
-    private static final boolean ENABLE_RESIZE_HANDLE = false;
+    private static final boolean ENABLE_ENTER_SPLIT = false;
 
     private int mMenuState;
     private boolean mAllowMenuTimeout = true;
@@ -139,7 +139,7 @@
     protected View mViewRoot;
     protected View mSettingsButton;
     protected View mDismissButton;
-    protected View mResizeHandle;
+    protected View mEnterSplitButton;
     protected View mTopEndContainer;
     protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
 
@@ -177,14 +177,23 @@
             }
         });
 
-        mResizeHandle = findViewById(R.id.resize_handle);
-        mResizeHandle.setAlpha(0);
+        mEnterSplitButton = findViewById(R.id.enter_split);
+        mEnterSplitButton.setAlpha(0);
+        mEnterSplitButton.setOnClickListener(v -> {
+            if (mMenuContainer.getAlpha() != 0) {
+                enterSplit();
+            }
+        });
+
+        findViewById(R.id.resize_handle).setAlpha(0);
+
         mActionsGroup = findViewById(R.id.actions_group);
         mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
                 R.dimen.pip_between_action_padding_land);
         mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
         mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
-                mResizeHandle, mSettingsButton, mDismissButton);
+                findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton,
+                mDismissButton);
         mDismissFadeOutDurationMs = context.getResources()
                 .getInteger(R.integer.config_pipExitAnimationDuration);
 
@@ -268,14 +277,13 @@
                     mSettingsButton.getAlpha(), 1f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 1f);
-            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(),
-                    ENABLE_RESIZE_HANDLE && showResizeHandle ? 1f : 0f);
+            ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
+                    mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f);
             if (menuState == MENU_STATE_FULL) {
                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
-                        resizeAnim);
+                        enterSplitAnim);
             } else {
-                mMenuContainerAnimator.playTogether(resizeAnim);
+                mMenuContainerAnimator.playTogether(enterSplitAnim);
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
@@ -328,7 +336,7 @@
         mMenuContainer.setAlpha(0f);
         mSettingsButton.setAlpha(0f);
         mDismissButton.setAlpha(0f);
-        mResizeHandle.setAlpha(0f);
+        mEnterSplitButton.setAlpha(0f);
     }
 
     void pokeMenu() {
@@ -368,9 +376,10 @@
                     mSettingsButton.getAlpha(), 0f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 0f);
-            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(), 0f);
-            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
+            ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
+                    mEnterSplitButton.getAlpha(), 0f);
+            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
+                    enterSplitAnim);
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
             mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
             mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -522,6 +531,14 @@
         }
     }
 
+    private void enterSplit() {
+        // Do not notify menu visibility when hiding the menu, the controller will do this when it
+        // handles the message
+        hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */,
+                ANIM_TYPE_HIDE);
+    }
+
+
     private void showSettings() {
         final Pair<ComponentName, Integer> topPipActivityInfo =
                 PipUtils.getTopPipActivity(mContext);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index dbd09fd..c634b7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -338,22 +338,29 @@
      * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
      *      * fullscreen depending on the display area's windowing mode.
      */
-    void expandLeavePip() {
-        expandLeavePip(false /* skipAnimation */);
+    void expandLeavePip(boolean skipAnimation) {
+        expandLeavePip(skipAnimation, false /* enterSplit */);
+    }
+
+    /**
+     * Resizes the pinned task to split-screen mode.
+     */
+    void expandIntoSplit() {
+        expandLeavePip(false, true /* enterSplit */);
     }
 
     /**
      * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
      * fullscreen depending on the display area's windowing mode.
      */
-    void expandLeavePip(boolean skipAnimation) {
+    private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
         if (DEBUG) {
             Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
         mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
-        mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
+        mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 9f2f6a5..570fd5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -139,7 +139,12 @@
 
         @Override
         public void onPipExpand() {
-            mMotionHelper.expandLeavePip();
+            mMotionHelper.expandLeavePip(false /* skipAnimation */);
+        }
+
+        @Override
+        public void onEnterSplit() {
+            mMotionHelper.expandIntoSplit();
         }
 
         @Override
@@ -899,7 +904,7 @@
                     // Expand to fullscreen if this is a double tap
                     // the PiP should be frozen until the transition ends
                     setTouchEnabled(false);
-                    mMotionHelper.expandLeavePip();
+                    mMotionHelper.expandLeavePip(false /* skipAnimation */);
                 }
             } else if (mMenuState != MENU_STATE_FULL) {
                 if (mPipBoundsState.isStashed()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index a2e9b64..00083d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -219,7 +219,7 @@
     public void movePipToFullscreen() {
         if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState));
 
-        mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
+        mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
         onPipDisappeared();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 836a6f6..7cf3baf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -41,10 +41,13 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.util.StagedSplitBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
@@ -62,6 +65,13 @@
     // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
     // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
     private final SparseIntArray mSplitTasks = new SparseIntArray();
+    /**
+     * Maps taskId to {@link StagedSplitBounds} for both taskIDs.
+     * Meaning there will be two taskId integers mapping to the same object.
+     * If there's any ordering to the pairing than we can probably just get away with only one
+     * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now.
+     */
+    private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>();
 
     /**
      * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -97,15 +107,20 @@
     /**
      * Adds a split pair. This call does not validate the taskIds, only that they are not the same.
      */
-    public void addSplitPair(int taskId1, int taskId2) {
+    public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) {
         if (taskId1 == taskId2) {
             return;
         }
         // Remove any previous pairs
         removeSplitPair(taskId1);
         removeSplitPair(taskId2);
+        mTaskSplitBoundsMap.remove(taskId1);
+        mTaskSplitBoundsMap.remove(taskId2);
+
         mSplitTasks.put(taskId1, taskId2);
         mSplitTasks.put(taskId2, taskId1);
+        mTaskSplitBoundsMap.put(taskId1, splitBounds);
+        mTaskSplitBoundsMap.put(taskId2, splitBounds);
     }
 
     /**
@@ -116,6 +131,8 @@
         if (pairedTaskId != INVALID_TASK_ID) {
             mSplitTasks.delete(taskId);
             mSplitTasks.delete(pairedTaskId);
+            mTaskSplitBoundsMap.remove(taskId);
+            mTaskSplitBoundsMap.remove(pairedTaskId);
         }
     }
 
@@ -203,7 +220,8 @@
             if (pairedTaskId != INVALID_TASK_ID) {
                 final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
                 rawMapping.remove(pairedTaskId);
-                recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo));
+                recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo,
+                        mTaskSplitBoundsMap.get(pairedTaskId)));
             } else {
                 recentTasks.add(new GroupedRecentTaskInfo(taskInfo));
             }
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 04058ed..7457be2 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
@@ -202,11 +202,25 @@
         return moveToSideStage(task, sideStagePosition);
     }
 
+    public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition,
+            WindowContainerTransaction wct) {
+        final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+        if (task == null) {
+            throw new IllegalArgumentException("Unknown taskId" + taskId);
+        }
+        return moveToSideStage(task, sideStagePosition, wct);
+    }
+
     public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
             @SplitPosition int sideStagePosition) {
         return mStageCoordinator.moveToSideStage(task, sideStagePosition);
     }
 
+    public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+            @SplitPosition int sideStagePosition, WindowContainerTransaction wct) {
+        return mStageCoordinator.moveToSideStage(task, sideStagePosition, wct);
+    }
+
     public boolean removeFromSideStage(int taskId) {
         return mStageCoordinator.removeFromSideStage(taskId);
     }
@@ -224,6 +238,11 @@
                 leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
     }
 
+    public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
+        moveToSideStage(taskId,
+                leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+    }
+
     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
         mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
     }
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 8471e1e..95886c8 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
@@ -96,6 +96,7 @@
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.StagedSplitBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -280,6 +281,11 @@
     boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
             @SplitPosition int sideStagePosition) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        return moveToSideStage(task, sideStagePosition, wct);
+    }
+
+    boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+            @SplitPosition int sideStagePosition, WindowContainerTransaction wct) {
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         setSideStagePosition(sideStagePosition, wct);
         mSideStage.evictAllChildren(evictWct);
@@ -691,11 +697,25 @@
         }
 
         mRecentTasks.ifPresent(recentTasks -> {
+            Rect topLeftBounds = mSplitLayout.getBounds1();
+            Rect bottomRightBounds = mSplitLayout.getBounds2();
             int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId();
             int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId();
+            boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+            int leftTopTaskId;
+            int rightBottomTaskId;
+            if (sideStageTopLeft) {
+                leftTopTaskId = sideStageTopTaskId;
+                rightBottomTaskId = mainStageTopTaskId;
+            } else {
+                leftTopTaskId = mainStageTopTaskId;
+                rightBottomTaskId = sideStageTopTaskId;
+            }
+            StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds,
+                    leftTopTaskId, rightBottomTaskId);
             if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
                 // Update the pair for the top tasks
-                recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId);
+                recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
             }
         });
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
index 0331ba1..603d05d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
@@ -30,25 +30,34 @@
 public class GroupedRecentTaskInfo implements Parcelable {
     public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1;
     public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2;
+    public @Nullable StagedSplitBounds mStagedSplitBounds;
 
     public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) {
-        this(task1, null);
+        this(task1, null, null);
     }
 
     public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1,
-            @Nullable ActivityManager.RecentTaskInfo task2) {
+            @Nullable ActivityManager.RecentTaskInfo task2,
+            @Nullable StagedSplitBounds stagedSplitBounds) {
         mTaskInfo1 = task1;
         mTaskInfo2 = task2;
+        mStagedSplitBounds = stagedSplitBounds;
     }
 
     GroupedRecentTaskInfo(Parcel parcel) {
         mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
         mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
+        mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR);
     }
 
     @Override
     public String toString() {
-        return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2);
+        String taskString = "Task1: " + getTaskInfo(mTaskInfo1)
+                + ", Task2: " + getTaskInfo(mTaskInfo2);
+        if (mStagedSplitBounds != null) {
+            taskString += ", SplitBounds: " + mStagedSplitBounds.toString();
+        }
+        return taskString;
     }
 
     private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) {
@@ -67,6 +76,7 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeTypedObject(mTaskInfo1, flags);
         parcel.writeTypedObject(mTaskInfo2, flags);
+        parcel.writeTypedObject(mStagedSplitBounds, flags);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
new file mode 100644
index 0000000..aadf792
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.util;
+
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container of various information needed to display split screen
+ * tasks/leashes/etc in Launcher
+ */
+public class StagedSplitBounds implements Parcelable {
+    public final Rect leftTopBounds;
+    public final Rect rightBottomBounds;
+    /** This rect represents the actual gap between the two apps */
+    public final Rect visualDividerBounds;
+    // This class is orientation-agnostic, so we compute both for later use
+    public final float topTaskPercent;
+    public final float leftTaskPercent;
+    /**
+     * If {@code true}, that means at the time of creation of this object, the
+     * split-screened apps were vertically stacked. This is useful in scenarios like
+     * rotation where the bounds won't change, but this variable can indicate what orientation
+     * the bounds were originally in
+     */
+    public final boolean appsStackedVertically;
+    public final int leftTopTaskId;
+    public final int rightBottomTaskId;
+
+    public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
+            int leftTopTaskId, int rightBottomTaskId) {
+        this.leftTopBounds = leftTopBounds;
+        this.rightBottomBounds = rightBottomBounds;
+        this.leftTopTaskId = leftTopTaskId;
+        this.rightBottomTaskId = rightBottomTaskId;
+
+        if (rightBottomBounds.top > leftTopBounds.top) {
+            // vertical apps, horizontal divider
+            this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
+                    leftTopBounds.right, rightBottomBounds.top);
+            appsStackedVertically = true;
+        } else {
+            // horizontal apps, vertical divider
+            this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
+                    rightBottomBounds.left, leftTopBounds.bottom);
+            appsStackedVertically = false;
+        }
+
+        leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
+        topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+    }
+
+    public StagedSplitBounds(Parcel parcel) {
+        leftTopBounds = parcel.readTypedObject(Rect.CREATOR);
+        rightBottomBounds = parcel.readTypedObject(Rect.CREATOR);
+        visualDividerBounds = parcel.readTypedObject(Rect.CREATOR);
+        topTaskPercent = parcel.readFloat();
+        leftTaskPercent = parcel.readFloat();
+        appsStackedVertically = parcel.readBoolean();
+        leftTopTaskId = parcel.readInt();
+        rightBottomTaskId = parcel.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeTypedObject(leftTopBounds, flags);
+        parcel.writeTypedObject(rightBottomBounds, flags);
+        parcel.writeTypedObject(visualDividerBounds, flags);
+        parcel.writeFloat(topTaskPercent);
+        parcel.writeFloat(leftTaskPercent);
+        parcel.writeBoolean(appsStackedVertically);
+        parcel.writeInt(leftTopTaskId);
+        parcel.writeInt(rightBottomTaskId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
+                + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId +  "\n"
+                + "Divider: " + visualDividerBounds + "\n"
+                + "AppsVertical? " + appsStackedVertically;
+    }
+
+    public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() {
+        @Override
+        public StagedSplitBounds createFromParcel(Parcel in) {
+            return new StagedSplitBounds(in);
+        }
+
+        @Override
+        public StagedSplitBounds[] newArray(int size) {
+            return new StagedSplitBounds[size];
+        }
+    };
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 0270093..0172cf32 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -50,6 +50,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -75,7 +76,8 @@
     @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
-    @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
+    @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen;
+    @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
@@ -99,8 +101,9 @@
                 mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
                 mPipBoundsAlgorithm, mMockPhonePipMenuController,
                 mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
-                mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController,
-                mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
+                mMockPipTransitionController, mMockOptionalLegacySplitScreen,
+                mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger,
+                mMockShellTaskOrganizer, mMainExecutor));
         mMainExecutor.flushAll();
         preparePipTaskOrg();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index a1e1231..19a5417 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -31,10 +33,9 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.ActivityManager;
-import android.app.WindowConfiguration;
 import android.content.Context;
+import android.graphics.Rect;
 import android.view.SurfaceControl;
-import android.window.TaskAppearedInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -45,6 +46,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.util.StagedSplitBounds;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -106,8 +108,11 @@
         setRawList(t1, t2, t3, t4, t5, t6);
 
         // Mark a couple pairs [t2, t4], [t3, t5]
-        mRecentTasksController.addSplitPair(t2.taskId, t4.taskId);
-        mRecentTasksController.addSplitPair(t3.taskId, t5.taskId);
+        StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4);
+        StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5);
+
+        mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
+        mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -126,7 +131,8 @@
         setRawList(t1, t2, t3);
 
         // Add a pair
-        mRecentTasksController.addSplitPair(t2.taskId, t3.taskId);
+        StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3);
+        mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
         reset(mRecentTasksController);
 
         // Remove one of the tasks and ensure the pair is removed
@@ -201,10 +207,23 @@
         int[] flattenedTaskIds = new int[recentTasks.size() * 2];
         for (int i = 0; i < recentTasks.size(); i++) {
             GroupedRecentTaskInfo pair = recentTasks.get(i);
-            flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId;
+            int taskId1 = pair.mTaskInfo1.taskId;
+            flattenedTaskIds[2 * i] = taskId1;
             flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null
                     ? pair.mTaskInfo2.taskId
                     : -1;
+
+            if (pair.mTaskInfo2 != null) {
+                assertNotNull(pair.mStagedSplitBounds);
+                int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId;
+                int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId;
+                // Unclear if pairs are ordered by split position, most likely not.
+                assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId);
+                assertTrue(bottomRightTaskId == taskId1
+                        || bottomRightTaskId == pair.mTaskInfo2.taskId);
+            } else {
+                assertNull(pair.mStagedSplitBounds);
+            }
         }
         assertTrue("Expected: " + Arrays.toString(expectedTaskIds)
                         + " Received: " + Arrays.toString(flattenedTaskIds),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java
new file mode 100644
index 0000000..ad73c56
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java
@@ -0,0 +1,94 @@
+package com.android.wm.shell.recents;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wm.shell.util.StagedSplitBounds;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StagedSplitBoundsTest {
+    private static final int DEVICE_WIDTH = 100;
+    private static final int DEVICE_LENGTH = 200;
+    private static final int DIVIDER_SIZE = 20;
+    private static final int TASK_ID_1 = 4;
+    private static final int TASK_ID_2 = 9;
+
+    // Bounds in screen space
+    private final Rect mTopRect = new Rect();
+    private final Rect mBottomRect = new Rect();
+    private final Rect mLeftRect = new Rect();
+    private final Rect mRightRect = new Rect();
+
+    @Before
+    public void setup() {
+        mTopRect.set(0, 0, DEVICE_WIDTH, DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2);
+        mBottomRect.set(0, DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2,
+                DEVICE_WIDTH, DEVICE_LENGTH);
+        mLeftRect.set(0, 0, DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, DEVICE_LENGTH);
+        mRightRect.set(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, 0,
+                DEVICE_WIDTH, DEVICE_LENGTH);
+    }
+
+    @Test
+    public void testVerticalStacked() {
+        StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+                TASK_ID_1, TASK_ID_2);
+        assertTrue(ssb.appsStackedVertically);
+    }
+
+    @Test
+    public void testHorizontalStacked() {
+        StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+                TASK_ID_1, TASK_ID_2);
+        assertFalse(ssb.appsStackedVertically);
+    }
+
+    @Test
+    public void testHorizontalDividerBounds() {
+        StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+                TASK_ID_1, TASK_ID_2);
+        Rect dividerBounds = ssb.visualDividerBounds;
+        assertEquals(0, dividerBounds.left);
+        assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top);
+        assertEquals(DEVICE_WIDTH, dividerBounds.right);
+        assertEquals(DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, dividerBounds.bottom);
+    }
+
+    @Test
+    public void testVerticalDividerBounds() {
+        StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+                TASK_ID_1, TASK_ID_2);
+        Rect dividerBounds = ssb.visualDividerBounds;
+        assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
+        assertEquals(0, dividerBounds.top);
+        assertEquals(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, dividerBounds.right);
+        assertEquals(DEVICE_LENGTH, dividerBounds.bottom);
+    }
+
+    @Test
+    public void testEqualVerticalTaskPercent() {
+        StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+                TASK_ID_1, TASK_ID_2);
+        float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
+        assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
+    }
+
+    @Test
+    public void testEqualHorizontalTaskPercent() {
+        StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+                TASK_ID_1, TASK_ID_2);
+        float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
+        assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
+    }
+}
diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h
index 9bbd0a9..29ef2b8 100644
--- a/libs/hwui/pipeline/skia/FunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/FunctorDrawable.h
@@ -34,6 +34,8 @@
  */
 class FunctorDrawable : public SkDrawable {
 public:
+    constexpr static const char* const TYPE_NAME = "FunctorDrawable";
+
     FunctorDrawable(int functor, SkCanvas* canvas)
             : mBounds(canvas->getLocalClipBounds())
             , mWebViewHandle(WebViewFunctorManager::instance().handleFor(functor)) {}
@@ -48,6 +50,8 @@
         mWebViewHandle->onRemovedFromTree();
     }
 
+    const char* getTypeName() const override { return TYPE_NAME; }
+
 protected:
     virtual SkRect onGetBounds() override { return mBounds; }
 
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp
index 6777c00..41e3687 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 #include "TransformCanvas.h"
+
+#include "FunctorDrawable.h"
 #include "HolePunch.h"
 #include "SkData.h"
 #include "SkDrawable.h"
@@ -35,7 +37,17 @@
 }
 
 void TransformCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) {
-    drawable->draw(this, matrix);
+    // TransformCanvas filters all drawing commands while maintaining the current
+    // clip stack and transformation. We need to draw most SkDrawables, since their
+    // draw calls may call methods that affect the clip stack and transformation. (Any
+    // actual draw commands will then be filtered out.) But FunctorDrawables are used
+    // as leaf nodes which issue self-contained OpenGL/Vulkan commands. These won't
+    // affect the clip stack + transformation, and in some cases cause problems (e.g. if
+    // the surface only has an alpha channel). See b/203960959
+    const auto* drawableName = drawable->getTypeName();
+    if (drawableName == nullptr || strcmp(drawableName, FunctorDrawable::TYPE_NAME) != 0) {
+        drawable->draw(this, matrix);
+    }
 }
 
 bool TransformCanvas::onFilter(SkPaint& paint) const {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 3c43f4a6..a383c1e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -70,6 +70,7 @@
     MediaRouter2Manager mRouterManager;
     @VisibleForTesting
     String mPackageName;
+    private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
     private MediaDevice mCurrentConnectedDevice;
     private LocalBluetoothManager mBluetoothManager;
@@ -83,6 +84,9 @@
         if (!TextUtils.isEmpty(packageName)) {
             mPackageName = packageName;
         }
+
+        mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
     }
 
     @Override
@@ -387,7 +391,9 @@
 
     @TargetApi(Build.VERSION_CODES.R)
     boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
-        return false;
+        return sessionInfo.isSystemSession() // System sessions are not remote
+                || mVolumeAdjustmentForRemoteGroupSessions
+                || sessionInfo.getSelectedRoutes().size() <= 1;
     }
 
     private void refreshDevices() {
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 1844288..0b3eccf 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -85,9 +85,10 @@
         val camSeed = Cam.fromInt(seedArgb)
         val hue = camSeed.hue
         val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA)
+        val tertiaryHue = wrapDegrees((hue + ACCENT3_HUE_SHIFT).toInt())
         accent1 = Shades.of(hue, chroma).toList()
         accent2 = Shades.of(hue, ACCENT2_CHROMA).toList()
-        accent3 = Shades.of(hue + ACCENT3_HUE_SHIFT, ACCENT3_CHROMA).toList()
+        accent3 = Shades.of(tertiaryHue.toFloat(), ACCENT3_CHROMA).toList()
         neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList()
         neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList()
     }
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4d2986f..7216ac6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -266,8 +266,6 @@
         <item name="android:paddingTop">12dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">24sp</item>
-        <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">marquee</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Subtitle">
@@ -275,8 +273,6 @@
         <item name="android:paddingTop">8dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">16sp</item>
-        <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">marquee</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Description">
@@ -284,8 +280,6 @@
         <item name="android:paddingTop">8dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">14sp</item>
-        <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">marquee</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Error">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
index ee6dea5..91a3912 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
@@ -20,6 +20,11 @@
  */
 interface FlagReader {
     /** Returns a boolean value for the given flag.  */
+    fun isEnabled(flag: BooleanFlag): Boolean {
+        return flag.default
+    }
+
+    /** Returns a boolean value for the given flag.  */
     fun isEnabled(id: Int, def: Boolean): Boolean {
         return def
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java
deleted file mode 100644
index 323b20e..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.recents.model;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * A group task in the recent tasks list.
- * TODO: Move this into Launcher
- */
-public class GroupTask {
-    public @NonNull Task task1;
-    public @Nullable Task task2;
-
-    public GroupTask(@NonNull Task t1, @Nullable Task t2) {
-        task1 = t1;
-        task2 = t2;
-    }
-
-    public GroupTask(@NonNull GroupTask group) {
-        task1 = new Task(group.task1);
-        task2 = group.task2 != null
-                ? new Task(group.task2)
-                : null;
-    }
-
-    public boolean containsTask(int taskId) {
-        return task1.key.id == taskId || (task2 != null && task2.key.id == taskId);
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 3f2ff74..3128ffd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -23,17 +23,14 @@
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.ViewDebug;
 
-import com.android.systemui.shared.recents.utilities.Utilities;
+import androidx.annotation.Nullable;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -202,8 +199,8 @@
      * The icon is the task description icon (if provided), which falls back to the activity icon,
      * which can then fall back to the application icon.
      */
-    public Drawable icon;
-    public ThumbnailData thumbnail;
+    @Nullable public Drawable icon;
+    @Nullable public ThumbnailData thumbnail;
     @ViewDebug.ExportedProperty(category="recents")
     @Deprecated
     public String title;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index b95123d..38eded8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -195,8 +195,13 @@
                     }
 
                     @Override
-                    public void onTaskAppeared(RemoteAnimationTarget app) {
-                        animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app));
+                    public void onTasksAppeared(RemoteAnimationTarget[] apps) {
+                        final RemoteAnimationTargetCompat[] compats =
+                                new RemoteAnimationTargetCompat[apps.length];
+                        for (int i = 0; i < apps.length; ++i) {
+                            compats[i] = new RemoteAnimationTargetCompat(apps[i]);
+                        }
+                        animationHandler.onTasksAppeared(compats);
                     }
                 };
             }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index a74de2e..48f1b76 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -39,5 +39,5 @@
      * Called when the task of an activity that has been started while the recents animation
      * was running becomes ready for control.
      */
-    void onTaskAppeared(RemoteAnimationTargetCompat app);
+    void onTasksAppeared(RemoteAnimationTargetCompat[] app);
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 99b6aed..954cf9f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -53,6 +53,7 @@
 import com.android.internal.util.DataClass;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
+import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
 /**
@@ -127,7 +128,7 @@
                 mToken = transition;
                 // This transition is for opening recents, so recents is on-top. We want to draw
                 // the current going-away task on top of recents, though, so move it to front
-                WindowContainerToken pausingTask = null;
+                final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>();
                 WindowContainerToken pipTask = null;
                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                     final TransitionInfo.Change change = info.getChanges().get(i);
@@ -138,7 +139,8 @@
                         if (taskInfo == null) {
                             continue;
                         }
-                        pausingTask = taskInfo.token;
+                        // Add to front since we are iterating backwards.
+                        pausingTasks.add(0, taskInfo.token);
                         if (taskInfo.pictureInPictureParams != null
                                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
                             pipTask = taskInfo.token;
@@ -150,7 +152,7 @@
                     t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);
                 }
                 t.apply();
-                mRecentsSession.setup(controller, info, finishedCallback, pausingTask, pipTask,
+                mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
                         leashMap, mToken);
                 recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
                         new Rect());
@@ -198,18 +200,18 @@
     static class RecentsControllerWrap extends RecentsAnimationControllerCompat {
         private RecentsAnimationControllerCompat mWrapped = null;
         private IRemoteTransitionFinishedCallback mFinishCB = null;
-        private WindowContainerToken mPausingTask = null;
+        private ArrayList<WindowContainerToken> mPausingTasks = null;
         private WindowContainerToken mPipTask = null;
         private TransitionInfo mInfo = null;
-        private SurfaceControl mOpeningLeash = null;
+        private ArrayList<SurfaceControl> mOpeningLeashes = null;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
         private PictureInPictureSurfaceTransaction mPipTransaction = null;
         private IBinder mTransition = null;
 
         void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
-                IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask,
-                WindowContainerToken pipTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
-                IBinder transition) {
+                IRemoteTransitionFinishedCallback finishCB,
+                ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask,
+                ArrayMap<SurfaceControl, SurfaceControl> leashMap, IBinder transition) {
             if (mInfo != null) {
                 throw new IllegalStateException("Trying to run a new recents animation while"
                         + " recents is already active.");
@@ -217,7 +219,7 @@
             mWrapped = wrapped;
             mInfo = info;
             mFinishCB = finishCB;
-            mPausingTask = pausingTask;
+            mPausingTasks = pausingTasks;
             mPipTask = pipTask;
             mLeashMap = leashMap;
             mTransition = transition;
@@ -226,36 +228,57 @@
         @SuppressLint("NewApi")
         boolean merge(TransitionInfo info, SurfaceControl.Transaction t,
                 RecentsAnimationListener recents) {
-            TransitionInfo.Change openingTask = null;
+            ArrayList<TransitionInfo.Change> openingTasks = null;
             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
                     if (change.getTaskInfo() != null) {
-                        if (openingTask != null) {
-                            Log.w(TAG, " Expecting to merge a task-open, but got >1 opening "
-                                    + "tasks");
+                        if (openingTasks == null) {
+                            openingTasks = new ArrayList<>();
                         }
-                        openingTask = change;
+                        openingTasks.add(change);
                     }
                 }
             }
-            if (openingTask == null) return false;
-            mOpeningLeash = openingTask.getLeash();
-            if (openingTask.getContainer().equals(mPausingTask)) {
-                // In this case, we are "returning" to the already running app, so just consume
+            if (openingTasks == null) return false;
+            int pauseMatches = 0;
+            for (int i = 0; i < openingTasks.size(); ++i) {
+                if (mPausingTasks.contains(openingTasks.get(i).getContainer())) {
+                    ++pauseMatches;
+                }
+                if (openingTasks.get(i).getContainer().equals(mPausingTasks.get(i))) {
+                    // In this case, we are "returning" to an already running app, so just consume
+                    // the merge and do nothing.
+                }
+            }
+            if (pauseMatches > 0) {
+                if (pauseMatches != mPausingTasks.size()) {
+                    // We are not really "returning" properly... something went wrong.
+                    throw new IllegalStateException("\"Concelling\" a recents transitions by "
+                            + "unpausing " + pauseMatches + " apps after pausing "
+                            + mPausingTasks.size() + " apps.");
+                }
+                // In this case, we are "returning" to an already running app, so just consume
                 // the merge and do nothing.
                 return true;
             }
-            // We are receiving a new opening task, so convert to onTaskAppeared.
             final int layer = mInfo.getChanges().size() * 3;
-            final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
-                    openingTask, layer, mInfo, t);
-            mLeashMap.put(mOpeningLeash, target.leash.mSurfaceControl);
-            t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
-            t.setLayer(target.leash.mSurfaceControl, layer);
-            t.hide(target.leash.mSurfaceControl);
-            t.apply();
-            recents.onTaskAppeared(target);
+            mOpeningLeashes = new ArrayList<>();
+            final RemoteAnimationTargetCompat[] targets =
+                    new RemoteAnimationTargetCompat[openingTasks.size()];
+            for (int i = 0; i < openingTasks.size(); ++i) {
+                mOpeningLeashes.add(openingTasks.get(i).getLeash());
+                // We are receiving new opening tasks, so convert to onTasksAppeared.
+                final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
+                        openingTasks.get(i), layer, mInfo, t);
+                mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
+                t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
+                t.setLayer(target.leash.mSurfaceControl, layer);
+                t.hide(target.leash.mSurfaceControl);
+                t.apply();
+                targets[i] = target;
+            }
+            recents.onTasksAppeared(targets);
             return true;
         }
 
@@ -292,21 +315,26 @@
             }
             if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint);
             try {
-                if (!toHome && mPausingTask != null && mOpeningLeash == null) {
+                if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
                     // The gesture went back to opening the app rather than continuing with
                     // recents, so end the transition by moving the app back to the top (and also
                     // re-showing it's task).
                     final WindowContainerTransaction wct = new WindowContainerTransaction();
-                    wct.reorder(mPausingTask, true /* onTop */);
                     final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                    t.show(mInfo.getChange(mPausingTask).getLeash());
+                    for (int i = mPausingTasks.size() - 1; i >= 0; ++i) {
+                        // reverse order so that index 0 ends up on top
+                        wct.reorder(mPausingTasks.get(i), true /* onTop */);
+                        t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+                    }
                     mFinishCB.onTransitionFinished(wct, t);
                 } else {
-                    if (mOpeningLeash != null) {
+                    if (mOpeningLeashes != null) {
                         // TODO: the launcher animation should handle this
                         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                        t.show(mOpeningLeash);
-                        t.setAlpha(mOpeningLeash, 1.f);
+                        for (int i = 0; i < mOpeningLeashes.size(); ++i) {
+                            t.show(mOpeningLeashes.get(i));
+                            t.setAlpha(mOpeningLeashes.get(i), 1.f);
+                        }
                         t.apply();
                     }
                     if (mPipTask != null && mPipTransaction != null) {
@@ -339,9 +367,9 @@
             // Reset all members.
             mWrapped = null;
             mFinishCB = null;
-            mPausingTask = null;
+            mPausingTasks = null;
             mInfo = null;
-            mOpeningLeash = null;
+            mOpeningLeashes = null;
             mLeashMap = null;
             mTransition = null;
         }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
index ef04619..acfa3c8 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -26,13 +26,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.util.Log;
 
+import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -62,14 +65,19 @@
 
     private final FlagManager mFlagManager;
     private final SecureSettings mSecureSettings;
+    private final Resources mResources;
     private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
 
     @Inject
-    public FeatureFlagManager(FlagManager flagManager,
-            SecureSettings secureSettings, Context context,
+    public FeatureFlagManager(
+            FlagManager flagManager,
+            Context context,
+            SecureSettings secureSettings,
+            @Main Resources resources,
             DumpManager dumpManager) {
         mFlagManager = flagManager;
         mSecureSettings = secureSettings;
+        mResources = resources;
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
         filter.addAction(ACTION_GET_FLAGS);
@@ -77,17 +85,32 @@
         dumpManager.registerDumpable(TAG, this);
     }
 
-    /** Return a {@link BooleanFlag}'s value. */
     @Override
-    public boolean isEnabled(int id, boolean defaultValue) {
+    public boolean isEnabled(BooleanFlag flag) {
+        int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
-            Boolean result = isEnabledInternal(id);
-            mBooleanFlagCache.put(id, result == null ? defaultValue : result);
+            boolean def = flag.getDefault();
+            if (flag.hasResourceOverride()) {
+                try {
+                    def = isEnabledInOverlay(flag.getResourceOverride());
+                } catch (Resources.NotFoundException e) {
+                    // no-op
+                }
+            }
+
+            mBooleanFlagCache.put(id, isEnabled(id, def));
         }
 
         return mBooleanFlagCache.get(id);
     }
 
+    /** Return a {@link BooleanFlag}'s value. */
+    @Override
+    public boolean isEnabled(int id, boolean defaultValue) {
+        Boolean result = isEnabledInternal(id);
+        return result == null ? defaultValue : result;
+    }
+
     /** Returns the stored value or null if not set. */
     private Boolean isEnabledInternal(int id) {
         try {
@@ -98,6 +121,10 @@
         return null;
     }
 
+    private boolean isEnabledInOverlay(@BoolRes int resId) {
+        return mResources.getBoolean(resId);
+    }
+
     /** Set whether a given {@link BooleanFlag} is enabled or not. */
     @Override
     public void setEnabled(int id, boolean value) {
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
index 6ff175f..0934b32 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.flags;
 
-import android.content.Context;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
@@ -24,7 +23,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -41,8 +39,7 @@
 public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
     SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
     @Inject
-    public FeatureFlagManager(
-            SecureSettings secureSettings, Context context, DumpManager dumpManager) {
+    public FeatureFlagManager(DumpManager dumpManager) {
         dumpManager.registerDumpable("SysUIFlags", this);
     }
 
@@ -53,6 +50,11 @@
     public void removeListener(Listener run) {}
 
     @Override
+    public boolean isEnabled(BooleanFlag flag) {
+        return isEnabled(flag.getId(), flag.getDefault());
+    }
+
+    @Override
     public boolean isEnabled(int key, boolean defaultValue) {
         mAccessedFlags.append(key, defaultValue);
         return defaultValue;
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index a383cab..ac463eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -31,7 +31,6 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.ViewController;
 
@@ -49,7 +48,6 @@
     private final StatusBarStateController mStatusBarStateController;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final KeyguardBypassController mBypassController;
     private final BatteryController mBatteryController;
     private final int mDozingColor = Color.WHITE;
     private int mLockScreenColor;
@@ -71,14 +69,12 @@
             BroadcastDispatcher broadcastDispatcher,
             BatteryController batteryController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardBypassController bypassController,
             @Main Resources resources
     ) {
         super(view);
         mStatusBarStateController = statusBarStateController;
         mBroadcastDispatcher = broadcastDispatcher;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mBypassController = bypassController;
         mBatteryController = batteryController;
 
         mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index ef3104a..2a0c285 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -27,7 +27,6 @@
 import android.widget.TextView;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 
 import java.util.Calendar;
 import java.util.Locale;
@@ -111,6 +110,28 @@
         super.onDetachedFromWindow();
     }
 
+    int getDozingWeight() {
+        if (useBoldedVersion()) {
+            return mDozingWeight + 100;
+        }
+        return mDozingWeight;
+    }
+
+    int getLockScreenWeight() {
+        if (useBoldedVersion()) {
+            return mLockScreenWeight + 100;
+        }
+        return mLockScreenWeight;
+    }
+
+    /**
+     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
+     */
+    boolean useBoldedVersion() {
+        // "Bold text" fontWeightAdjustment is 300.
+        return getResources().getConfiguration().fontWeightAdjustment > 100;
+    }
+
     void refreshTime() {
         mTime.setTimeInMillis(System.currentTimeMillis());
         setText(DateFormat.format(mFormat, mTime));
@@ -162,7 +183,7 @@
         }
 
         setTextStyle(
-                mDozingWeight,
+                getDozingWeight(),
                 -1 /* text size, no update */,
                 mLockScreenColor,
                 false /* animate */,
@@ -171,7 +192,7 @@
                 null /* onAnimationEnd */);
 
         setTextStyle(
-                mLockScreenWeight,
+                getLockScreenWeight(),
                 -1 /* text size, no update */,
                 mLockScreenColor,
                 true, /* animate */
@@ -180,35 +201,22 @@
                 null /* onAnimationEnd */);
     }
 
-    void animateDisappear() {
-        if (mTextAnimator == null) {
-            return;
-        }
-
-        setTextStyle(
-                0 /* weight */,
-                -1 /* text size, no update */,
-                null /* color, no update */,
-                true /* animate */,
-                KeyguardBypassController.BYPASS_FADE_DURATION /* duration */,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-    }
-
     void animateCharge(DozeStateGetter dozeStateGetter) {
         if (mTextAnimator == null || mTextAnimator.isRunning()) {
             // Skip charge animation if dozing animation is already playing.
             return;
         }
         Runnable startAnimPhase2 = () -> setTextStyle(
-                dozeStateGetter.isDozing() ? mDozingWeight : mLockScreenWeight/* weight */,
+                dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */,
                 -1,
                 null,
                 true /* animate */,
                 CHARGE_ANIM_DURATION_PHASE_1,
                 0 /* delay */,
                 null /* onAnimationEnd */);
-        setTextStyle(dozeStateGetter.isDozing() ? mLockScreenWeight : mDozingWeight/* weight */,
+        setTextStyle(dozeStateGetter.isDozing()
+                        ? getLockScreenWeight()
+                        : getDozingWeight()/* weight */,
                 -1,
                 null,
                 true /* animate */,
@@ -218,7 +226,7 @@
     }
 
     void animateDoze(boolean isDozing, boolean animate) {
-        setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
+        setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */,
                 -1,
                 isDozing ? mDozingColor : mLockScreenColor,
                 animate,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 1931c0a..905495d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -167,7 +167,6 @@
                         mBroadcastDispatcher,
                         mBatteryController,
                         mKeyguardUpdateMonitor,
-                        mBypassController,
                         mResources);
         mClockViewController.init();
 
@@ -178,7 +177,6 @@
                         mBroadcastDispatcher,
                         mBatteryController,
                         mKeyguardUpdateMonitor,
-                        mBypassController,
                         mResources);
         mLargeClockViewController.init();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 85bc8f7..d27bc67 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2044,17 +2044,17 @@
     }
 
     /**
-     * @return true if there's at least one udfps enrolled
+     * @return true if there's at least one udfps enrolled for the current user.
      */
     public boolean isUdfpsEnrolled() {
         return mIsUdfpsEnrolled;
     }
 
     /**
-     * @return if udfps is available on this device. will return true even if the user hasn't
-     * enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
+     * @return true if udfps HW is supported on this device. Can return true even if the user has
+     * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
-    public boolean isUdfpsAvailable() {
+    public boolean isUdfpsSupported() {
         return mAuthController.getUdfpsProps() != null
                 && !mAuthController.getUdfpsProps().isEmpty();
     }
@@ -2102,7 +2102,7 @@
         }
 
         updateUdfpsEnrolled(getCurrentUser());
-        final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsEnrolled());
+        final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
         final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         if (runningOrRestarting && !shouldListenForFingerprint) {
@@ -2407,7 +2407,7 @@
             } else {
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback, null /* handler */,
-                        FingerprintManager.SENSOR_ID_ANY, userId);
+                        FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */);
             }
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
@@ -2990,7 +2990,7 @@
 
     /**
      * Register to receive notifications about general keyguard information
-     * (see {@link InfoCallback}.
+     * (see {@link KeyguardUpdateMonitorCallback}.
      *
      * @param callback The callback to register
      */
@@ -3388,11 +3388,11 @@
                     + " expected=" + (shouldListenForFingerprint(isUdfpsEnrolled()) ? 1 : 0));
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
-            pw.println("    udfpsEnrolled=" + isUdfpsEnrolled());
             pw.println("    mFingerprintLockedOut=" + mFingerprintLockedOut);
             pw.println("    mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent);
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
-            if (isUdfpsEnrolled()) {
+            if (isUdfpsSupported()) {
+                pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
                 pw.println("        bouncerVisible=" + mBouncer);
                 pw.println("        mStatusBarState="
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index e115c34..b77db8f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -121,6 +121,13 @@
                             .setStartDelay(delay);
                 }
                 animator.start();
+            } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
+                mKeyguardViewVisibilityAnimating = true;
+
+                // Ask the screen off animation controller to animate the keyguard visibility for us
+                // since it may need to be cancelled due to keyguard lifecycle events.
+                mUnlockedScreenOffAnimationController.animateInKeyguard(
+                        mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else if (mLastOccludedState && !isOccluded) {
                 // An activity was displayed over the lock screen, and has now gone away
                 mView.setVisibility(View.VISIBLE);
@@ -132,13 +139,6 @@
                         .alpha(1f)
                         .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
                         .start();
-            } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
-                mKeyguardViewVisibilityAnimating = true;
-
-                // Ask the screen off animation controller to animate the keyguard visibility for us
-                // since it may need to be cancelled due to keyguard lifecycle events.
-                mUnlockedScreenOffAnimationController.animateInKeyguard(
-                        mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else {
                 mView.setVisibility(View.VISIBLE);
                 mView.setAlpha(1f);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8a0b5b8..c7be3ce 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -435,7 +435,7 @@
         boolean wasUdfpsSupported = mUdfpsSupported;
         boolean wasUdfpsEnrolled = mUdfpsEnrolled;
 
-        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+        mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported();
         mView.setUseBackground(mUdfpsSupported);
 
         mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index 59d9aff..d2703f5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -22,6 +22,7 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static java.util.Objects.requireNonNull;
 
@@ -659,6 +660,7 @@
                         | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                 PixelFormat.TRANSLUCENT);
         params.receiveInsetsIgnoringZOrder = true;
+        params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
         params.windowAnimations = android.R.style.Animation_Translucent;
         params.gravity = Gravity.START | Gravity.TOP;
         params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index ec17d4e..90a1e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.PointF
@@ -29,7 +31,9 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.commandline.Command
@@ -41,13 +45,10 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
+import com.android.systemui.util.leak.RotationUtils
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.util.leak.RotationUtils
-
-private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L
 
 /***
  * Controls the ripple effect that shows when authentication is successful.
@@ -141,11 +142,12 @@
 
     private fun showUnlockedRipple() {
         notificationShadeWindowController.setForcePluginOpen(true, this)
-        val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock
         val lightRevealScrim = statusBar.lightRevealScrim
-        if (useCircleReveal) {
-            lightRevealScrim?.revealEffect = circleReveal!!
-            startLightRevealScrimOnKeyguardFadingAway = true
+        if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
+            circleReveal?.let {
+                lightRevealScrim?.revealEffect = it
+                startLightRevealScrimOnKeyguardFadingAway = true
+            }
         }
 
         mView.startUnlockedRipple(
@@ -160,19 +162,29 @@
         if (keyguardStateController.isKeyguardFadingAway) {
             val lightRevealScrim = statusBar.lightRevealScrim
             if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
-                val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
+                ValueAnimator.ofFloat(.1f, 1f).apply {
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN
                     duration = RIPPLE_ANIMATION_DURATION
                     startDelay = keyguardStateController.keyguardFadingAwayDelay
                     addUpdateListener { animator ->
                         if (lightRevealScrim.revealEffect != circleReveal) {
-                            // if the something else took over the reveal, let's do nothing.
+                            // if something else took over the reveal, let's do nothing.
                             return@addUpdateListener
                         }
                         lightRevealScrim.revealAmount = animator.animatedValue as Float
                     }
+                    addListener(object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator?) {
+                            // Reset light reveal scrim to the default, so the StatusBar
+                            // can handle any subsequent light reveal changes
+                            // (ie: from dozing changes)
+                            if (lightRevealScrim.revealEffect == circleReveal) {
+                                lightRevealScrim.revealEffect = LiftReveal
+                            }
+                        }
+                    })
+                    start()
                 }
-                revealAnimator.start()
                 startLightRevealScrimOnKeyguardFadingAway = false
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index d64f9b3..34f4415 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -17,22 +17,11 @@
 package com.android.systemui.flags;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
-import android.util.SparseArray;
 import android.widget.Toast;
 
-import androidx.annotation.BoolRes;
-
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
 import javax.inject.Inject;
 
@@ -43,31 +32,13 @@
  */
 @SysUISingleton
 public class FeatureFlags {
-    private final Resources mResources;
     private final FlagReader mFlagReader;
     private final Context mContext;
-    private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
-    private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
-    private final SparseArray<Boolean> mCachedFlags = new SparseArray<>();
 
     @Inject
-    public FeatureFlags(@Main Resources resources, FlagReader flagReader, Context context) {
-        mResources = resources;
+    public FeatureFlags(FlagReader flagReader, Context context) {
         mFlagReader = flagReader;
         mContext = context;
-
-        flagReader.addListener(mListener);
-    }
-
-    private final FlagReader.Listener mListener = id -> {
-        if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
-            mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
-        }
-    };
-
-    @VisibleForTesting
-    void addFlag(Flag<?> flag) {
-        mFlagMap.put(flag.getId(), flag);
     }
 
     /**
@@ -75,32 +46,7 @@
      * @return The value of the flag.
      */
     public boolean isEnabled(BooleanFlag flag) {
-        boolean def = flag.getDefault();
-        if (flag.hasResourceOverride()) {
-            try {
-                def = isEnabledInOverlay(flag.getResourceOverride());
-            } catch (Resources.NotFoundException e) {
-                // no-op
-            }
-        }
-        return mFlagReader.isEnabled(flag.getId(), def);
-    }
-
-    /**
-     * @param flag The {@link IntFlag} of interest.
-
-    /** Add a listener for a specific flag. */
-    public void addFlagListener(Flag<?> flag, Listener listener) {
-        mListeners.putIfAbsent(flag.getId(), new ArrayList<>());
-        mListeners.get(flag.getId()).add(listener);
-        mFlagMap.putIfAbsent(flag.getId(), flag);
-    }
-
-    /** Remove a listener for a specific flag. */
-    public void removeFlagListener(Flag<?> flag, Listener listener) {
-        if (mListeners.containsKey(flag.getId())) {
-            mListeners.get(flag.getId()).remove(listener);
-        }
+        return mFlagReader.isEnabled(flag);
     }
 
     public void assertLegacyPipelineEnabled() {
@@ -205,20 +151,4 @@
     public static boolean isProviderModelSettingEnabled(Context context) {
         return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
     }
-
-    private boolean isEnabledInOverlay(@BoolRes int resId) {
-        synchronized (mCachedFlags) {
-            if (!mCachedFlags.contains(resId)) {
-                mCachedFlags.put(resId, mResources.getBoolean(resId));
-            }
-
-            return mCachedFlags.get(resId);
-        }
-    }
-
-    /** Simple interface for beinga alerted when a specific flag changes value. */
-    public interface Listener {
-        /** */
-        void onFlagChanged(Flag<?> flag);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 42dd886..1981269 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -79,6 +79,7 @@
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     private final boolean mAboveStatusbar;
+    private final boolean mVolumeAdjustmentForRemoteGroupSessions;
     private final NotificationEntryManager mNotificationEntryManager;
     @VisibleForTesting
     final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@@ -111,6 +112,8 @@
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mUiEventLogger = uiEventLogger;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
     }
 
     void start(@NonNull Callback cb) {
@@ -477,7 +480,9 @@
     }
 
     boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
-        return !isActiveRemoteDevice(device);
+        // TODO(b/202500642): Also enable volume control for remote non-group sessions.
+        return !isActiveRemoteDevice(device)
+            || mVolumeAdjustmentForRemoteGroupSessions;
     }
 
     private final MediaController.Callback mCb = new MediaController.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 6a1eae7..bc023cc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -24,9 +24,6 @@
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.containsType;
@@ -131,10 +128,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.shared.rotation.RotationButton;
-import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -614,8 +611,6 @@
         mDeviceProvisionedController.addCallback(mUserSetupListener);
         mNotificationShadeDepthController.addListener(mDepthListener);
 
-        updateAccessibilityButtonModeIfNeeded();
-
         return barView;
     }
 
@@ -1406,34 +1401,6 @@
         updateSystemUiStateFlags(a11yFlags);
     }
 
-    private void updateAccessibilityButtonModeIfNeeded() {
-        final int mode = Settings.Secure.getIntForUser(mContentResolver,
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
-
-        // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural
-        // mode, so we don't need to update it.
-        if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
-            return;
-        }
-
-        // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to
-        // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
-        if (QuickStepContract.isGesturalMode(mNavBarMode)
-                && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
-            Settings.Secure.putIntForUser(mContentResolver,
-                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
-                    UserHandle.USER_CURRENT);
-            // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
-            // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
-        } else if (!QuickStepContract.isGesturalMode(mNavBarMode)
-                && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
-            Settings.Secure.putIntForUser(mContentResolver,
-                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                    ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
-        }
-    }
-
     public void updateSystemUiStateFlags(int a11yFlags) {
         if (a11yFlags < 0) {
             a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
@@ -1551,6 +1518,9 @@
     @Override
     public void onNavigationModeChanged(int mode) {
         mNavBarMode = mode;
+        // update assistant entry points on system navigation radio button click
+        updateAssistantEntrypoints();
+
         if (!QuickStepContract.isGesturalMode(mode)) {
             // Reset the override alpha
             if (getBarTransitions() != null) {
@@ -1558,7 +1528,6 @@
             }
         }
         updateScreenPinningGestures();
-        updateAccessibilityButtonModeIfNeeded();
 
         if (!canShowSecondaryHandle()) {
             resetSecondaryHandle();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 4959c7d..3dc79c4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -16,10 +16,14 @@
 
 package com.android.systemui.navigationbar;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -27,6 +31,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
@@ -46,6 +52,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -142,6 +149,8 @@
         }
         final int oldMode = mNavMode;
         mNavMode = mode;
+        updateAccessibilityButtonModeIfNeeded();
+
         mHandler.post(() -> {
             // create/destroy nav bar based on nav mode only in unfolded state
             if (oldMode != mNavMode) {
@@ -157,6 +166,35 @@
         });
     }
 
+    private void updateAccessibilityButtonModeIfNeeded() {
+        ContentResolver contentResolver = mContext.getContentResolver();
+        final int mode = Settings.Secure.getIntForUser(contentResolver,
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+
+        // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural
+        // mode, so we don't need to update it.
+        if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+            return;
+        }
+
+        // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to
+        // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
+        if (QuickStepContract.isGesturalMode(mNavMode)
+                && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
+            Settings.Secure.putIntForUser(contentResolver,
+                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
+                    UserHandle.USER_CURRENT);
+            // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
+            // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
+        } else if (!QuickStepContract.isGesturalMode(mNavMode)
+                && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
+            Settings.Secure.putIntForUser(contentResolver,
+                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+                    ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+        }
+    }
+
     /** @see #initializeTaskbarIfNecessary() */
     private boolean updateNavbarForTaskbar() {
         boolean taskbarShown = initializeTaskbarIfNecessary();
@@ -222,6 +260,8 @@
      */
     public void createNavigationBars(final boolean includeDefaultDisplay,
             RegisterStatusBarResult result) {
+        updateAccessibilityButtonModeIfNeeded();
+
         // Don't need to create nav bar on the default display if we initialize TaskBar.
         final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
                 && !initializeTaskbarIfNecessary();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 74ebfe5..1c00887 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -818,7 +818,7 @@
     }
 
     private void showTryFingerprintMsg(int msgId, String a11yString) {
-        if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
+        if (mKeyguardUpdateMonitor.isUdfpsSupported()) {
             // if udfps available, there will always be a tappable affordance to unlock
             // For example, the lock icon
             if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index cbb3aba..da2b85e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -173,7 +173,7 @@
         }
 
         // Record the to-be mState and mLastState
-        recordHistoricalState(state, mState);
+        recordHistoricalState(state /* newState */, mState /* lastState */, false);
 
         // b/139259891
         if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) {
@@ -206,6 +206,7 @@
     @Override
     public void setUpcomingState(int nextState) {
         mUpcomingState = nextState;
+        recordHistoricalState(mUpcomingState /* newState */, mState /* lastState */, true);
     }
 
     @Override
@@ -505,31 +506,36 @@
         }
     }
 
-    private void recordHistoricalState(int currentState, int lastState) {
+    private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
         HistoricalState state = mHistoricalRecords[mHistoryIndex];
-        state.mState = currentState;
+        state.mNewState = newState;
         state.mLastState = lastState;
         state.mTimestamp = System.currentTimeMillis();
+        state.mUpcoming = upcoming;
     }
 
     /**
      * For keeping track of our previous state to help with debugging
      */
     private static class HistoricalState {
-        int mState;
+        int mNewState;
         int mLastState;
         long mTimestamp;
+        boolean mUpcoming;
 
         @Override
         public String toString() {
             if (mTimestamp != 0) {
                 StringBuilder sb = new StringBuilder();
-                sb.append("state=").append(mState)
-                        .append(" (").append(describe(mState)).append(")");
-                sb.append("lastState=").append(mLastState).append(" (").append(describe(mLastState))
+                if (mUpcoming) {
+                    sb.append("upcoming-");
+                }
+                sb.append("newState=").append(mNewState)
+                        .append("(").append(describe(mNewState)).append(")");
+                sb.append(" lastState=").append(mLastState).append("(").append(describe(mLastState))
                         .append(")");
-                sb.append("timestamp=")
+                sb.append(" timestamp=")
                         .append(DateFormat.format("MM-dd HH:mm:ss", mTimestamp));
 
                 return sb.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index e273727..32659e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2206,7 +2206,7 @@
         mQs.setExpanded(mQsExpanded);
     }
 
-    private void setQsExpansion(float height) {
+    void setQsExpansion(float height) {
         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
         mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
         if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
@@ -2250,7 +2250,13 @@
         int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
         setQSClippingBounds();
-        mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
+
+        // Only need to notify the notification stack when we're not in split screen mode. If we
+        // do, then the notification panel starts scrolling along with the QS.
+        if (!mShouldUseSplitNotificationShade) {
+            mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
+        }
+
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 91df1c5..cbaa468 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -230,7 +230,6 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.WallpaperController;
@@ -2386,6 +2385,8 @@
 
         if (mLightRevealScrim != null) {
             pw.println(
+                    "mLightRevealScrim.getRevealEffect(): " + mLightRevealScrim.getRevealEffect());
+            pw.println(
                     "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount());
         }
 
@@ -3371,17 +3372,24 @@
             return;
         }
 
-        if (wakingUp && mWakefulnessLifecycle.getLastWakeReason()
-                == PowerManager.WAKE_REASON_POWER_BUTTON
-                || !wakingUp && mWakefulnessLifecycle.getLastSleepReason()
-                == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) {
+        final boolean wakingUpFromPowerButton = wakingUp
+                && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
+                && mWakefulnessLifecycle.getLastWakeReason()
+                == PowerManager.WAKE_REASON_POWER_BUTTON;
+        final boolean sleepingFromPowerButton = !wakingUp
+                && mWakefulnessLifecycle.getLastSleepReason()
+                == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON;
+
+        if (wakingUpFromPowerButton || sleepingFromPowerButton) {
             mLightRevealScrim.setRevealEffect(mPowerButtonReveal);
+            mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount());
         } else if (!wakingUp || !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
             // If we're going to sleep, but it's not from the power button, use the default reveal.
             // If we're waking up, only use the default reveal if the biometric controller didn't
             // already set it to the circular reveal because we're waking up from a fingerprint/face
             // auth.
             mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
+            mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index cd5865f..426bc91 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -259,8 +259,13 @@
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
                 reevaluateSystemTheme(true /* forceReload */);
             } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
-                mAcceptColorEvents = true;
-                Log.i(TAG, "Allowing color events again");
+                if (intent.getBooleanExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, false)) {
+                    mAcceptColorEvents = true;
+                    Log.i(TAG, "Wallpaper changed, allowing color events again");
+                } else {
+                    Log.i(TAG, "Wallpaper changed from background app, "
+                            + "keep deferring color events. Accepting: " + mAcceptColorEvents);
+                }
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index e570598..cd6a778 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -33,7 +33,11 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IVolumeController;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RoutingSessionInfo;
 import android.media.VolumePolicy;
+import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession.Token;
 import android.net.Uri;
@@ -71,6 +75,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -118,6 +123,7 @@
     private final Context mContext;
     private final Looper mWorkerLooper;
     private final PackageManager mPackageManager;
+    private final MediaRouter2Manager mRouter2Manager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private AudioManager mAudio;
     private IAudioService mAudioService;
@@ -179,6 +185,7 @@
         mWorkerLooper = theadFactory.buildLooperOnNewThread(
                 VolumeDialogControllerImpl.class.getSimpleName());
         mWorker = new W(mWorkerLooper);
+        mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
         mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
         mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
         mAudio = audioManager;
@@ -1149,16 +1156,16 @@
         private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
 
         private int mNextStream = DYNAMIC_STREAM_START_INDEX;
-        private final boolean mShowRemoteSessions;
+        private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
         public MediaSessionsCallbacks(Context context) {
-            mShowRemoteSessions = context.getResources().getBoolean(
-                    com.android.internal.R.bool.config_volumeShowRemoteSessions);
+            mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
         }
 
         @Override
         public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
-            if (mShowRemoteSessions) {
+            if (showForSession(token)) {
                 addStream(token, "onRemoteUpdate");
 
                 int stream = 0;
@@ -1190,7 +1197,7 @@
 
         @Override
         public void onRemoteVolumeChanged(Token token, int flags) {
-            if (mShowRemoteSessions) {
+            if (showForSession(token)) {
                 addStream(token, "onRemoteVolumeChanged");
                 int stream = 0;
                 synchronized (mRemoteStreams) {
@@ -1214,7 +1221,7 @@
 
         @Override
         public void onRemoteRemoved(Token token) {
-            if (mShowRemoteSessions) {
+            if (showForSession(token)) {
                 int stream = 0;
                 synchronized (mRemoteStreams) {
                     if (!mRemoteStreams.containsKey(token)) {
@@ -1233,14 +1240,41 @@
         }
 
         public void setStreamVolume(int stream, int level) {
-            if (mShowRemoteSessions) {
-                final Token t = findToken(stream);
-                if (t == null) {
-                    Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
-                    return;
-                }
-                mMediaSessions.setVolume(t, level);
+            final Token token = findToken(stream);
+            if (token == null) {
+                Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
+                return;
             }
+            if (showForSession(token)) {
+                mMediaSessions.setVolume(token, level);
+            }
+        }
+
+        private boolean showForSession(Token token) {
+            if (mVolumeAdjustmentForRemoteGroupSessions) {
+                return true;
+            }
+            MediaController ctr = new MediaController(mContext, token);
+            String packageName = ctr.getPackageName();
+            List<RoutingSessionInfo> sessions =
+                    mRouter2Manager.getRoutingSessions(packageName);
+            boolean foundNonSystemSession = false;
+            boolean isGroup = false;
+            for (RoutingSessionInfo session : sessions) {
+                if (!session.isSystemSession()) {
+                    foundNonSystemSession = true;
+                    int selectedRouteCount = session.getSelectedRoutes().size();
+                    if (selectedRouteCount > 1) {
+                        isGroup = true;
+                        break;
+                    }
+                }
+            }
+            if (!foundNonSystemSession) {
+                Log.d(TAG, "No routing session for " + packageName);
+                return false;
+            }
+            return !isGroup;
         }
 
         private Token findToken(int stream) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 1ee6f70..ff5960b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -472,7 +472,8 @@
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
 
-        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt());
+        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+                        anyInt());
         verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
index 2fa32ba..6347638 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -53,8 +53,6 @@
 public class FeatureFlagManagerTest extends SysuiTestCase {
     FeatureFlagManager mFeatureFlagManager;
 
-    @Mock private FlagManager mFlagManager;
-    @Mock private SecureSettings mSecureSettings;
     @Mock private Context mContext;
     @Mock private DumpManager mDumpManager;
 
@@ -62,14 +60,11 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlagManager = new FeatureFlagManager(mSecureSettings, mContext, mDumpManager);
+        mFeatureFlagManager = new FeatureFlagManager(mDumpManager);
     }
 
     @After
     public void onFinished() {
-        // SecureSettings and Context are provided for constructor consistency with the
-        // debug version of the FeatureFlagManager, but should never be used.
-        verifyZeroInteractions(mSecureSettings, mContext);
         // The dump manager should be registered with even for the release version, but that's it.
         verify(mDumpManager).registerDumpable(anyString(), any());
         verifyNoMoreInteractions(mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
deleted file mode 100644
index 30e9b51..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-@SmallTest
-public class FeatureFlagsTest extends SysuiTestCase {
-
-    @Mock Resources mResources;
-    @Mock FlagReader mFeatureFlagReader;
-
-    private FeatureFlags mFeatureFlags;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mFeatureFlagReader.isEnabled(anyInt(), anyBoolean())).thenAnswer(
-                (Answer<Boolean>) invocation -> invocation.getArgument(1));
-
-        mFeatureFlags = new FeatureFlags(mResources, mFeatureFlagReader, getContext());
-    }
-
-    @Test
-    public void testAddListener() {
-        Flag<?> flag = new BooleanFlag(1);
-        mFeatureFlags.addFlag(flag);
-
-        // Assert and capture that a plugin listener was added.
-        ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
-                ArgumentCaptor.forClass(FlagReader.Listener.class);
-        verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
-        FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
-
-        // Signal a change. No listeners, so no real effect.
-        pluginListener.onFlagChanged(flag.getId());
-
-        // Add a listener for the flag
-        final Flag<?>[] changedFlag = {null};
-        FeatureFlags.Listener listener = f -> changedFlag[0] = f;
-        mFeatureFlags.addFlagListener(flag, listener);
-
-        // No changes seen yet.
-        assertThat(changedFlag[0]).isNull();
-
-        // Signal a change.
-        pluginListener.onFlagChanged(flag.getId());
-
-        // Assert that the change was for the correct flag.
-        assertThat(changedFlag[0]).isEqualTo(flag);
-    }
-
-    @Test
-    public void testRemoveListener() {
-        Flag<?> flag = new BooleanFlag(1);
-        mFeatureFlags.addFlag(flag);
-
-        // Assert and capture that a plugin listener was added.
-        ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
-                ArgumentCaptor.forClass(FlagReader.Listener.class);
-        verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
-        FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
-
-        // Add a listener for the flag
-        final Flag<?>[] changedFlag = {null};
-        FeatureFlags.Listener listener = f -> changedFlag[0] = f;
-        mFeatureFlags.addFlagListener(flag, listener);
-
-        // Signal a change.
-        pluginListener.onFlagChanged(flag.getId());
-
-        // Assert that the change was for the correct flag.
-        assertThat(changedFlag[0]).isEqualTo(flag);
-
-        changedFlag[0] = null;
-
-        // Now remove the listener.
-        mFeatureFlags.removeFlagListener(flag, listener);
-        // Signal a change.
-        pluginListener.onFlagChanged(flag.getId());
-        // Assert that the change was not triggered
-        assertThat(changedFlag[0]).isNull();
-    }
-
-    @Test
-    public void testBooleanDefault() {
-        BooleanFlag flag = new BooleanFlag(1, true);
-
-        mFeatureFlags.addFlag(flag);
-
-        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
-    }
-
-    @Test
-    public void testBooleanResourceOverlay() {
-        int resourceId = 12;
-        BooleanFlag flag = new BooleanFlag(1, false, resourceId);
-        when(mResources.getBoolean(resourceId)).thenReturn(true);
-        when(mResources.getResourceEntryName(resourceId)).thenReturn("flag");
-
-        mFeatureFlags.addFlag(flag);
-
-        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
index df11284..5a4bb86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -40,7 +40,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import org.junit.After;
@@ -69,8 +68,6 @@
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
-    private KeyguardBypassController mBypassController;
-    @Mock
     private Resources mResources;
 
     private MockitoSession mStaticMockSession;
@@ -99,7 +96,6 @@
                 mBroadcastDispatcher,
                 mBatteryController,
                 mKeyguardUpdateMonitor,
-                mBypassController,
                 mResources
         );
         mAnimatableClockController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 5e73dbc..d64319b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -212,6 +212,7 @@
     @Test
     public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
         // GIVEN fp sensor location is not available pre-init
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
         when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
         when(mAuthController.getUdfpsProps()).thenReturn(null);
         mLockIconViewController.init();
@@ -232,7 +233,7 @@
     }
 
     @Test
-    public void testLockIconViewBackgroundEnabledWhenUdfpsIsAvailable() {
+    public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
         // GIVEN Udpfs sensor location is available
         setupUdfps();
 
@@ -247,9 +248,9 @@
     }
 
     @Test
-    public void testLockIconViewBackgroundDisabledWhenUdfpsIsUnavailable() {
-        // GIVEN Udfps sensor location is not available
-        when(mAuthController.getUdfpsSensorLocation()).thenReturn(null);
+    public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
+        // GIVEN Udfps sensor location is not supported
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
 
         mLockIconViewController.init();
         captureAttachListener();
@@ -365,6 +366,7 @@
     }
 
     private Pair<Integer, PointF> setupUdfps() {
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         final PointF udfpsLocation = new PointF(50, 75);
         final int radius = 33;
         final FingerprintSensorPropertiesInternal fpProps =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index bc86ef9..8cd7d94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -22,6 +22,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.graphics.cam.Cam;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Assert;
@@ -90,4 +91,13 @@
         List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
         Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f));
     }
+
+    @Test
+    public void testTertiaryHueWrapsProperly() {
+        int colorInt = 0xffB3588A; // H350 C50 T50
+        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */);
+        int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2);
+        Cam cam = Cam.fromInt(tertiaryMid);
+        Assert.assertEquals(cam.getHue(), 50.0, 10.0);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 01f7fae..cb0d87a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -701,7 +701,7 @@
 
         // GIVEN fingerprint is also running (not udfps)
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
 
         mController.setVisible(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index f89bbe8..766471b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -152,7 +152,7 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_setsTheme() {
+    public void onWallpaperColorsChanged_setsTheme_whenForeground() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
@@ -180,13 +180,43 @@
 
         // But should change theme after changing wallpapers
         clearInvocations(mThemeOverlayApplier);
-        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true);
+        mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
                 null, null), WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
 
     @Test
+    public void onWallpaperColorsChanged_setsTheme_skipWhenBackground() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
+
+        // Should not change theme after changing wallpapers, if intent doesn't have
+        // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true.
+        clearInvocations(mThemeOverlayApplier);
+        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
+                null, null), WallpaperManager.FLAG_SYSTEM);
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -455,7 +485,9 @@
         // Regression test: null events should not reset the internal state and allow colors to be
         // applied again.
         clearInvocations(mThemeOverlayApplier);
-        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true);
+        mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
                 any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 5c0efd3..c9462d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -101,6 +101,11 @@
         // Initial non-set value
         when(mRingerModeLiveData.getValue()).thenReturn(-1);
         when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
+        // Enable group volume adjustments
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
+                true);
+
         mCallback = mock(VolumeDialogControllerImpl.C.class);
         mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cd743f9..b5f3389 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -31,7 +31,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
-import static android.app.ActivityManager.StopBgUsersOnSwitch;
+import static android.app.ActivityManager.StopUserOnSwitch;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.AppOpsManager.OP_NONE;
@@ -15099,8 +15099,8 @@
     }
 
     @Override
-    public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) {
-        mUserController.setStopBackgroundUsersOnSwitch(value);
+    public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
+        mUserController.setStopUserOnSwitch(value);
     }
 
     @Override
@@ -16402,8 +16402,8 @@
         }
 
         @Override
-        public void setStopBackgroundUsersOnSwitch(int value) {
-            ActivityManagerService.this.setStopBackgroundUsersOnSwitch(value);
+        public void setStopUserOnSwitch(int value) {
+            ActivityManagerService.this.setStopUserOnSwitch(value);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 60b2149..31e48fb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -331,7 +331,7 @@
                 case "get-isolated-pids":
                     return runGetIsolatedProcesses(pw);
                 case "set-stop-user-on-switch":
-                    return runSetStopBackgroundUsersOnSwitch(pw);
+                    return runSetStopUserOnSwitch(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -3166,25 +3166,24 @@
         return 0;
     }
 
-    private int runSetStopBackgroundUsersOnSwitch(PrintWriter pw) throws RemoteException {
+    private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException {
         mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                "setStopBackgroundUsersOnSwitch()");
+                "setStopUserOnSwitch()");
         String arg = getNextArg();
         if (arg == null) {
-            Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): resetting to default value");
-            mInternal.setStopBackgroundUsersOnSwitch(
-                    ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT);
+            Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value");
+            mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT);
             pw.println("Reset to default value");
             return 0;
         }
 
         boolean stop = Boolean.parseBoolean(arg);
         int value = stop
-                ? ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE
-                : ActivityManager.STOP_BG_USERS_ON_SWITCH_FALSE;
+                ? ActivityManager.STOP_USER_ON_SWITCH_TRUE
+                : ActivityManager.STOP_USER_ON_SWITCH_FALSE;
 
-        Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): setting to %d (%b)", value, stop);
-        mInternal.setStopBackgroundUsersOnSwitch(value);
+        Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop);
+        mInternal.setStopUserOnSwitch(value);
         pw.println("Set to " + stop);
 
         return 0;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b8be6c5..319fa94 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -19,9 +19,9 @@
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT;
-import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE;
-import static android.app.ActivityManager.StopBgUsersOnSwitch;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
+import static android.app.ActivityManager.StopUserOnSwitch;
 import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
@@ -376,7 +376,7 @@
      * user is switched.
      */
     @GuardedBy("mLock")
-    private @StopBgUsersOnSwitch int mStopBgUsersOnSwitch = STOP_BG_USERS_ON_SWITCH_DEFAULT;
+    private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT;
 
     UserController(ActivityManagerService service) {
         this(new Injector(service));
@@ -418,29 +418,27 @@
         }
     }
 
-    void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) {
+    void setStopUserOnSwitch(@StopUserOnSwitch int value) {
         if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
                 == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                 == PackageManager.PERMISSION_DENIED) {
             throw new SecurityException(
                     "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to "
-                            + "call setStopBackgroundUsersOnSwitch()");
+                            + "call setStopUserOnSwitch()");
         }
 
         synchronized (mLock) {
-            Slogf.i(TAG, "setStopBackgroundUsersOnSwitch(): %d -> %d",
-                    mStopBgUsersOnSwitch, value);
-            mStopBgUsersOnSwitch = value;
+            Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value);
+            mStopUserOnSwitch = value;
         }
     }
 
-    private boolean shouldStopBackgroundUsersOnSwitch() {
+    private boolean shouldStopUserOnSwitch() {
         synchronized (mLock) {
-            if (mStopBgUsersOnSwitch != STOP_BG_USERS_ON_SWITCH_DEFAULT) {
-                final boolean value = mStopBgUsersOnSwitch == STOP_BG_USERS_ON_SWITCH_TRUE;
-                Slogf.i(TAG, "isStopBackgroundUsersOnSwitch(): returning overridden value (%b)",
-                        value);
+            if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) {
+                final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE;
+                Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value);
                 return value;
             }
         }
@@ -1834,7 +1832,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) {
+    private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) {
         // Never stop system user
         if (oldUserId == UserHandle.USER_SYSTEM) {
             return;
@@ -1842,18 +1840,17 @@
         boolean hasRestriction =
                 hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
         synchronized (mLock) {
-            // If running in background is disabled or mStopBackgroundUsersOnSwitch mode,
-            // stop the user.
-            boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch();
+            // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
+            boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch();
             if (!disallowRunInBg) {
                 if (DEBUG_MU) {
-                    Slogf.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related "
-                            + "users", oldUserId);
+                    Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users",
+                            oldUserId);
                 }
                 return;
             }
             if (DEBUG_MU) {
-                Slogf.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users",
+                Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users",
                         oldUserId);
             }
             stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
@@ -1956,7 +1953,7 @@
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
         stopGuestOrEphemeralUserIfBackground(oldUserId);
-        stopBackgroundUsersOnSwitchIfEnforced(oldUserId);
+        stopUserOnSwitchIfEnforced(oldUserId);
     }
 
     private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
@@ -2646,9 +2643,8 @@
             pw.println("  mTargetUserId:" + mTargetUserId);
             pw.println("  mLastActiveUsers:" + mLastActiveUsers);
             pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
-            pw.println("  shouldStopBackgroundUsersOnSwitch():"
-                    + shouldStopBackgroundUsersOnSwitch());
-            pw.println("  mStopBgUsersOnSwitch:" + mStopBgUsersOnSwitch);
+            pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
+            pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
             pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
             pw.println("  mInitialized:" + mInitialized);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e0775d4..f42870b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1381,7 +1381,8 @@
 
                 Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
                         + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
-                        + " requestId: " + requestId);
+                        + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: "
+                        + promptInfo.isIgnoreEnrollmentState());
 
                 if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
                     // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index cd0ff10..a5a3542 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -83,6 +83,7 @@
     final List<Pair<BiometricSensor, Integer>> ineligibleSensors;
     final boolean credentialAvailable;
     final boolean confirmationRequested;
+    final boolean ignoreEnrollmentState;
 
     static PreAuthInfo create(ITrustManager trustManager,
             DevicePolicyManager devicePolicyManager,
@@ -114,7 +115,8 @@
                 @AuthenticatorStatus int status = getStatusForBiometricAuthenticator(
                         devicePolicyManager, settingObserver, sensor, userId, opPackageName,
                         checkDevicePolicyManager, requestedStrength,
-                        promptInfo.getAllowedSensorIds());
+                        promptInfo.getAllowedSensorIds(),
+                        promptInfo.isIgnoreEnrollmentState());
 
                 Slog.d(TAG, "Package: " + opPackageName
                         + " Sensor ID: " + sensor.id
@@ -130,7 +132,8 @@
         }
 
         return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
-                eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested);
+                eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
+                promptInfo.isIgnoreEnrollmentState());
     }
 
     /**
@@ -145,7 +148,8 @@
             BiometricService.SettingObserver settingObserver,
             BiometricSensor sensor, int userId, String opPackageName,
             boolean checkDevicePolicyManager, int requestedStrength,
-            @NonNull List<Integer> requestedSensorIds) {
+            @NonNull List<Integer> requestedSensorIds,
+            boolean ignoreEnrollmentState) {
 
         if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
             return BIOMETRIC_NO_HARDWARE;
@@ -167,7 +171,8 @@
                 return BIOMETRIC_HARDWARE_NOT_DETECTED;
             }
 
-            if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)) {
+            if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)
+                    && !ignoreEnrollmentState) {
                 return BIOMETRIC_NOT_ENROLLED;
             }
 
@@ -238,7 +243,7 @@
     private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
             boolean credentialRequested, List<BiometricSensor> eligibleSensors,
             List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
-            boolean confirmationRequested) {
+            boolean confirmationRequested, boolean ignoreEnrollmentState) {
         mBiometricRequested = biometricRequested;
         mBiometricStrengthRequested = biometricStrengthRequested;
         this.credentialRequested = credentialRequested;
@@ -247,6 +252,7 @@
         this.ineligibleSensors = ineligibleSensors;
         this.credentialAvailable = credentialAvailable;
         this.confirmationRequested = confirmationRequested;
+        this.ignoreEnrollmentState = ignoreEnrollmentState;
     }
 
     private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index f35bb7f..c5d33ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -281,7 +281,7 @@
         @Override // Binder call
         public long authenticate(final IBinder token, final long operationId,
                 final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
-                final String opPackageName) {
+                final String opPackageName, boolean ignoreEnrollmentState) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int callingUserId = UserHandle.getCallingUserId();
@@ -333,7 +333,8 @@
                     && sensorProps != null && sensorProps.isAnyUdfpsType()) {
                 identity = Binder.clearCallingIdentity();
                 try {
-                    return authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+                    return authenticateWithPrompt(operationId, sensorProps, userId, receiver,
+                            ignoreEnrollmentState);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
@@ -347,7 +348,8 @@
                 final long operationId,
                 @NonNull final FingerprintSensorPropertiesInternal props,
                 final int userId,
-                final IFingerprintServiceReceiver receiver) {
+                final IFingerprintServiceReceiver receiver,
+                boolean ignoreEnrollmentState) {
 
             final Context context = getUiContext();
             final Executor executor = context.getMainExecutor();
@@ -368,6 +370,7 @@
                             })
                     .setAllowedSensorIds(new ArrayList<>(
                             Collections.singletonList(props.sensorId)))
+                    .setIgnoreEnrollmentState(ignoreEnrollmentState)
                     .build();
 
             final BiometricPrompt.AuthenticationCallback promptCallback =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 82b34c3..73baf79 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -223,7 +223,7 @@
     public Context getSettingsContext(int displayId) {
         if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
             final Context systemUiContext = ActivityThread.currentActivityThread()
-                    .getSystemUiContext(displayId);
+                    .createSystemUiContext(displayId);
             final Context windowContext = systemUiContext.createWindowContext(
                     WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
             mSettingsContext = new ContextThemeWrapper(
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 607218e..b424c20 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -146,6 +146,12 @@
     }
 
     @Override
+    public boolean canHandleVolumeKey() {
+        // TODO: Implement when MediaSession2 starts to get key events.
+        return false;
+    }
+
+    @Override
     public int getSessionPolicies() {
         synchronized (mLock) {
             return mPolicies;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 1525cd4..e4ed0e5 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -26,7 +26,9 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaMetadata;
+import android.media.MediaRouter2Manager;
 import android.media.Rating;
+import android.media.RoutingSessionInfo;
 import android.media.VolumeProvider;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -50,6 +52,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -121,6 +124,7 @@
     private final SessionCb mSessionCb;
     private final MediaSessionService mService;
     private final Context mContext;
+    private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
     private final Object mLock = new Object();
     private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
@@ -180,6 +184,8 @@
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mAudioAttrs = DEFAULT_ATTRIBUTES;
         mPolicies = policies;
+        mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
 
         // May throw RemoteException if the session app is killed.
         mSessionCb.mCb.asBinder().linkToDeath(this, 0);
@@ -449,6 +455,33 @@
     }
 
     @Override
+    public boolean canHandleVolumeKey() {
+        if (isPlaybackTypeLocal() || mVolumeAdjustmentForRemoteGroupSessions) {
+            return true;
+        }
+        MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
+        List<RoutingSessionInfo> sessions =
+                mRouter2Manager.getRoutingSessions(mPackageName);
+        boolean foundNonSystemSession = false;
+        boolean isGroup = false;
+        for (RoutingSessionInfo session : sessions) {
+            if (!session.isSystemSession()) {
+                foundNonSystemSession = true;
+                int selectedRouteCount = session.getSelectedRoutes().size();
+                if (selectedRouteCount > 1) {
+                    isGroup = true;
+                    break;
+                }
+            }
+        }
+        if (!foundNonSystemSession) {
+            Log.d(TAG, "No routing session for " + mPackageName);
+            return false;
+        }
+        return !isGroup;
+    }
+
+    @Override
     public int getSessionPolicies() {
         synchronized (mLock) {
             return mPolicies;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 3c50597..8f01f02 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -131,6 +131,13 @@
             KeyEvent ke, int sequenceId, ResultReceiver cb);
 
     /**
+     * Returns whether the media session can handle volume key events.
+     *
+     * @return True if this media session can handle volume key events, false otherwise.
+     */
+    boolean canHandleVolumeKey();
+
+    /**
      * Get session policies from custom policy provider set when MediaSessionRecord is instantiated.
      * If custom policy does not exist, will return null.
      */
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index c4c21df..b75ba75 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -325,8 +325,7 @@
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
-            // Do not send the volume key events to remote sessions.
-            if (record.checkPlaybackActiveState(true) && record.isPlaybackTypeLocal()) {
+            if (record.checkPlaybackActiveState(true) && record.canHandleVolumeKey()) {
                 mCachedVolumeDefault = record;
                 return record;
             }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index a51ed09..2f353d1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.wallpaper;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -791,6 +792,7 @@
     private final Context mContext;
     private final WindowManagerInternal mWindowManagerInternal;
     private final IPackageManager mIPackageManager;
+    private final ActivityManager mActivityManager;
     private final MyPackageMonitor mMonitor;
     private final AppOpsManager mAppOpsManager;
 
@@ -939,6 +941,11 @@
          */
         WallpaperColors primaryColors;
 
+        /**
+         * If the wallpaper was set from a foreground app (instead of from a background service).
+         */
+        public boolean fromForegroundApp;
+
         WallpaperConnection connection;
         long lastDiedTime;
         boolean wallpaperUpdating;
@@ -1688,6 +1695,7 @@
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
         mMonitor = new MyPackageMonitor();
         mColorsChangedListeners = new SparseArray<>();
 
@@ -2648,6 +2656,9 @@
             }
         }
 
+        final boolean fromForegroundApp = Binder.withCleanCallingIdentity(() ->
+                mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND);
+
         synchronized (mLock) {
             if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
             WallpaperData wallpaper;
@@ -2670,6 +2681,7 @@
                     wallpaper.imageWallpaperPending = true;
                     wallpaper.whichPending = which;
                     wallpaper.setComplete = completion;
+                    wallpaper.fromForegroundApp = fromForegroundApp;
                     wallpaper.cropHint.set(cropHint);
                     wallpaper.allowBackup = allowBackup;
                 }
@@ -3052,6 +3064,7 @@
         wallpaper.callbacks.finishBroadcast();
 
         final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, wallpaper.fromForegroundApp);
         mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index cf9783f..38a48570 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -20,11 +20,11 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK;
 import static android.os.Build.IS_USER;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
@@ -1009,6 +1009,8 @@
                     final int windowType = windowState.mAttrs.type;
                     if (isExcludedWindowType(windowType)
                             || ((windowState.mAttrs.privateFlags
+                            & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
+                            || ((windowState.mAttrs.privateFlags
                             & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
                         continue;
                     }
@@ -1073,7 +1075,6 @@
                         }
                     }
                 }
-
                 visibleWindows.clear();
 
                 mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
@@ -1110,9 +1111,6 @@
 
             private boolean isExcludedWindowType(int windowType) {
                 return windowType == TYPE_MAGNIFICATION_OVERLAY
-                        // Omit the touch region to avoid the cut out of the magnification
-                        // bounds because nav bar panel is unmagnifiable.
-                        || windowType == TYPE_NAVIGATION_BAR_PANEL
                         // Omit the touch region of window magnification to avoid the cut out of the
                         // magnification and the magnified center of window magnification could be
                         // in the bounds
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index d137436..f878562 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -770,10 +770,6 @@
         if (compatStateInfo.mLastLoggedActivity == r) {
             compatStateInfo.mLastLoggedActivity = null;
         }
-        if (compatStateInfo.mVisibleActivities.isEmpty()) {
-            // No need to keep the entry if there are no visible activities.
-            mPackageUidToCompatStateInfo.remove(packageUid);
-        }
     }
 
     /**
@@ -1269,13 +1265,14 @@
      *   activity.
      *   <li>If the current state is NOT_VISIBLE, there is a previously logged state for the
      *   package UID and there are no other visible activities with the same package UID.
-     *   <li>The last logged activity with the same package UID is either {@code activity} or the
-     *   last logged state is NOT_VISIBLE or NOT_LETTERBOXED.
+     *   <li>The last logged activity with the same package UID is either {@code activity} (or an
+     *   activity that has been removed) or the last logged state is NOT_VISIBLE or NOT_LETTERBOXED.
      * </ul>
      *
      * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code
-     * activity} wasn't, looks for the first visible activity with the same package UID that has
-     * a letterboxed state, or a non-letterboxed state if there isn't one, and logs that state.
+     * activity} (or an activity that has been removed) wasn't, looks for the first visible activity
+     * with the same package UID that has a letterboxed state, or a non-letterboxed state if
+     * there isn't one, and logs that state.
      *
      * <p>This method assumes that the caller is wrapping the call with a synchronized block so
      * that there won't be a race condition between two activities with the same package.
@@ -1311,14 +1308,14 @@
 
         if (!isVisible && !visibleActivities.isEmpty()) {
             // There is another visible activity for this package UID.
-            if (activity == lastLoggedActivity) {
+            if (lastLoggedActivity == null || activity == lastLoggedActivity) {
                 // Make sure a new visible state is logged if needed.
                 findAppCompatStateToLog(compatStateInfo, packageUid);
             }
             return;
         }
 
-        if (activity != lastLoggedActivity
+        if (lastLoggedActivity != null && activity != lastLoggedActivity
                 && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE
                 && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) {
             // Another visible activity for this package UID has logged a letterboxed state.
@@ -1332,15 +1329,25 @@
      * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed
      * state, or a non-letterboxed state if there isn't one, and logs that state for the given
      * {@code packageUid}.
+     *
+     * <p>If there is a visible activity in {@code compatStateInfo} with the same state as the
+     * last logged state for the given {@code packageUid}, changes the last logged activity to
+     * reference the first such activity without actually logging the same state twice.
      */
     private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) {
         final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities;
+        final int lastLoggedState = compatStateInfo.mLastLoggedState;
 
         ActivityRecord activityToLog = null;
         int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
         for (int i = 0; i < visibleActivities.size(); i++) {
             ActivityRecord activity = visibleActivities.get(i);
             int state = activity.getAppCompatState();
+            if (state == lastLoggedState) {
+                // Change last logged activity without logging the same state twice.
+                compatStateInfo.mLastLoggedActivity = activity;
+                return;
+            }
             if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
                 // This shouldn't happen.
                 Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: "
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 535a061..f947773 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -934,6 +934,10 @@
                 voiceInteraction);
         applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                 voiceInteraction);
+        final RecentsAnimationController rac = mService.getRecentsAnimationController();
+        if (rac != null) {
+            rac.sendTasksAppeared();
+        }
 
         for (int i = 0; i < openingApps.size(); ++i) {
             openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9ef3ef2..29b1a80 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -34,7 +34,6 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.os.Build.VERSION_CODES.N;
-import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.util.RotationUtils.deltaRotation;
@@ -63,7 +62,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
@@ -4998,12 +4996,6 @@
             reconfigureDisplayLocked();
             onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
             mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
-            // Attach the SystemUiContext to this DisplayContent the get latest configuration.
-            // Note that the SystemUiContext will be removed automatically if this DisplayContent
-            // is detached.
-            mWmService.mWindowContextListenerController.registerWindowContainerListener(
-                    getDisplayUiContext().getWindowContextToken(), this, SYSTEM_UID,
-                    INVALID_WINDOW_TYPE, null /* options */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1d3c56e..296bd78 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -112,6 +112,9 @@
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.LoadedApk;
+import android.app.ResourcesManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -448,7 +451,7 @@
                 : service.mContext.createDisplayContext(displayContent.getDisplay());
         mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.mUiContext
                 : service.mAtmService.mSystemThread
-                        .getSystemUiContext(displayContent.getDisplayId());
+                        .createSystemUiContext(displayContent.getDisplayId());
         mDisplayContent = displayContent;
         mLock = service.getWindowManagerLock();
 
@@ -2254,8 +2257,19 @@
 
         // For non-system users, ensure that the resources are loaded from the current
         // user's package info (see ContextImpl.createDisplayContext)
-        mCurrentUserResources = uiContext.createContextAsUser(UserHandle.of(userId), 0 /* flags*/)
-                .getResources();
+        final LoadedApk pi = ActivityThread.currentActivityThread().getPackageInfo(
+                uiContext.getPackageName(), null, 0, userId);
+        mCurrentUserResources = ResourcesManager.getInstance().getResources(null,
+                pi.getResDir(),
+                null /* splitResDirs */,
+                pi.getOverlayDirs(),
+                pi.getOverlayPaths(),
+                pi.getApplicationInfo().sharedLibraryFiles,
+                mDisplayContent.getDisplayId(),
+                null /* overrideConfig */,
+                uiContext.getResources().getCompatibilityInfo(),
+                null /* classLoader */,
+                null /* loaders */);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 081a53e..fe21e5f 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -16,10 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.UiModeManager.MODE_NIGHT_AUTO;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
-
 import android.annotation.NonNull;
+import android.content.res.Configuration;
 import android.os.Environment;
 import android.os.LocaleList;
 import android.util.AtomicFile;
@@ -303,7 +301,7 @@
         }
 
         boolean isResetNightMode() {
-            return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
+            return mNightMode == Configuration.UI_MODE_NIGHT_UNDEFINED;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index b4963c5..b54208d 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -173,10 +172,8 @@
      * to avoid flickering when running PiP animation across different orientations.
      */
     void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() {
-        final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea()
-                .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        final ActivityRecord topFullscreen = topFullscreenTask != null
-                ? topFullscreenTask.topRunningActivity() : null;
+        final ActivityRecord topFullscreen = mDisplayContent.getActivity(
+                a -> a.fillsParent() && !a.getTask().inMultiWindowMode());
         if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 2057b1c..fd4b63e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -163,6 +164,8 @@
     private boolean mNavigationBarAttachedToApp;
     private ActivityRecord mNavBarAttachedApp;
 
+    private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>();
+
     /**
      * An app transition listener to cancel the recents animation only after the app transition
      * starts or is canceled.
@@ -732,11 +735,19 @@
                 return;
             }
             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target);
-            try {
-                mRunner.onTaskAppeared(target);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to report task appeared", e);
-            }
+            mPendingTaskAppears.add(target);
+        }
+    }
+
+    void sendTasksAppeared() {
+        if (mPendingTaskAppears.isEmpty() || mRunner == null) return;
+        try {
+            final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray(
+                    new RemoteAnimationTarget[0]);
+            mRunner.onTasksAppeared(targets);
+            mPendingTaskAppears.clear();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to report task appeared", e);
         }
     }
 
@@ -744,10 +755,15 @@
             OnAnimationFinishedCallback finishedCallback) {
         final SparseBooleanArray recentTaskIds =
                 mService.mAtmService.getRecentTasks().getRecentTaskIds();
+        // The target must be built off the root task (the leaf task surface would be cropped
+        // within the root surface). However, recents only tracks leaf task ids, so we'll replace
+        // the task-id with the leaf id.
+        final Task leafTask = task.getTopLeafTask();
+        int taskId = leafTask.mTaskId;
         TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
-                !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback);
-        mPendingNewTaskTargets.add(task.mTaskId);
-        return adapter.createRemoteAnimationTarget();
+                !recentTaskIds.get(taskId), true /* hidden */, finishedCallback);
+        mPendingNewTaskTargets.add(taskId);
+        return adapter.createRemoteAnimationTarget(taskId);
     }
 
     void logRecentsAnimationStartTime(int durationMs) {
@@ -782,7 +798,8 @@
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-            final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget();
+            final RemoteAnimationTarget target =
+                    taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID);
             if (target != null) {
                 targets.add(target);
             } else {
@@ -995,6 +1012,8 @@
             removeAnimation(taskAdapter);
             taskAdapter.onCleanup();
         }
+        // Should already be empty, but clean-up pending task-appears in-case they weren't sent.
+        mPendingTaskAppears.clear();
 
         for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
             final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
@@ -1224,7 +1243,14 @@
             mLocalBounds.offsetTo(tmpPos.x, tmpPos.y);
         }
 
-        RemoteAnimationTarget createRemoteAnimationTarget() {
+        /**
+         * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus
+         *                       can differ from taskInfo. This mismatch is needed, however, in
+         *                       some cases where we are animating root tasks but need need leaf
+         *                       ids for identification. If this is INVALID (-1), then mTaskId
+         *                       will be used.
+         */
+        RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId) {
             final ActivityRecord topApp = mTask.getTopVisibleActivity();
             final WindowState mainWindow = topApp != null
                     ? topApp.findMainWindow()
@@ -1238,7 +1264,10 @@
             final int mode = topApp.getActivityType() == mTargetActivityType
                     ? MODE_OPENING
                     : MODE_CLOSING;
-            mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
+            if (overrideTaskId < 0) {
+                overrideTaskId = mTask.mTaskId;
+            }
+            mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash,
                     !topApp.fillsParent(), new Rect(),
                     insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
                     mLocalBounds, mBounds, mTask.getWindowConfiguration(),
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3ffa62d..b1eca9d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2585,7 +2585,7 @@
             // starts. Instead, we expect home activities to be launched when the system is ready
             // (ActivityManagerService#systemReady).
             if (mService.isBooted() || mService.isBooting()) {
-                startSystemDecorations(display);
+                startSystemDecorations(display.mDisplayContent);
             }
             // Drop any cached DisplayInfos associated with this display id - the values are now
             // out of date given this display added event.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a799e7c..1b7a012 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1126,7 +1126,11 @@
         if (inMultiWindowMode() || !hasChild()) return false;
         if (intent != null) {
             final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
-            return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
+            final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null;
+            final boolean isLockTaskModeViolation = task != null
+                    && mAtmService.getLockTaskController().isLockTaskModeViolation(task);
+            return (intent.getFlags() & returnHomeFlags) == returnHomeFlags
+                    && !isLockTaskModeViolation;
         }
         final Task bottomTask = getBottomMostTask();
         return bottomTask != this && bottomTask.returnsToHomeRootTask();
@@ -2377,6 +2381,16 @@
         return true;
     }
 
+    /** Return the top-most leaf-task under this one, or this task if it is a leaf. */
+    public Task getTopLeafTask() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task child = mChildren.get(i).asTask();
+            if (child == null) continue;
+            return child.getTopLeafTask();
+        }
+        return this;
+    }
+
     int getDescendantTaskCount() {
         final int[] currentCount = {0};
         final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 86e356a..bc53041 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -45,7 +45,7 @@
  *
  * <ul>
  *   <li>When a {@link WindowContext} is created, it registers the listener via
- *     {@link WindowManagerService#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)}
+ *     {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)}
  *     automatically.</li>
  *   <li>When the {@link WindowContext} adds the first window to the screen via
  *     {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)},
@@ -53,7 +53,7 @@
  *     to corresponding {@link WindowToken} via this controller.</li>
  *   <li>When the {@link WindowContext} is GCed, it unregisters the previously
  *     registered listener via
- *     {@link WindowManagerService#detachWindowContextFromWindowContainer(IBinder)}.
+ *     {@link WindowManagerService#unregisterWindowContextListener(IBinder)}.
  *     {@link WindowManagerService} is also responsible for removing the
  *     {@link WindowContext} created {@link WindowToken}.</li>
  * </ul>
@@ -68,7 +68,7 @@
 
     /**
      * Registers the listener to a {@code container} which is associated with
-     * a {@code clientToken}, which is a {@link android.window.WindowContext} representation. If the
+     * a {@code clientToken}, which is a {@link android.app.WindowContext} representation. If the
      * listener associated with {@code clientToken} hasn't been initialized yet, create one
      * {@link WindowContextListenerImpl}. Otherwise, the listener associated with
      * {@code clientToken} switches to listen to the {@code container}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3edcd5c..9bede97 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2741,9 +2741,6 @@
     @Override
     public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
             type, int displayId, Bundle options) {
-        if (clientToken == null) {
-            throw new IllegalArgumentException("clientToken must not be null!");
-        }
         final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
                 "attachWindowContextToDisplayArea", false /* printLog */);
         final int callingUid = Binder.getCallingUid();
@@ -2834,39 +2831,6 @@
         }
     }
 
-    @Override
-    public Configuration attachToDisplayContent(IBinder clientToken, int displayId) {
-        if (clientToken == null) {
-            throw new IllegalArgumentException("clientToken must not be null!");
-        }
-        final int callingUid = Binder.getCallingUid();
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                // We use "getDisplayContent" instead of "getDisplayContentOrCreate" because
-                // this method may be called in DisplayPolicy's constructor and may cause
-                // infinite loop. In this scenario, we early return here and switch to do the
-                // registration in DisplayContent#onParentChanged at DisplayContent initialization.
-                final DisplayContent dc = mRoot.getDisplayContent(displayId);
-                if (dc == null) {
-                    if (Binder.getCallingPid() != myPid()) {
-                        throw new WindowManager.InvalidDisplayException("attachToDisplayContent: "
-                                + "trying to attach to a non-existing display:" + displayId);
-                    }
-                    // Early return if this method is invoked from system process.
-                    // See above comments for more detail.
-                    return null;
-                }
-
-                mWindowContextListenerController.registerWindowContainerListener(clientToken, dc,
-                        callingUid, INVALID_WINDOW_TYPE, null /* options */);
-                return dc.getConfiguration();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
     /** Returns {@code true} if this binder is a registered window token. */
     @Override
     public boolean isWindowToken(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 5dd01a5..bd8d116 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -581,115 +581,18 @@
                 task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task);
                 break;
             }
-            case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
+            case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: {
                 effects |= setAdjacentRootsHierarchyOp(hop);
                 break;
-        }
-        // The following operations may change task order so they are skipped while in lock task
-        // mode. The above operations are still allowed because they don't move tasks. And it may
-        // be necessary such as clearing launch root after entering lock task mode.
-        if (isInLockTaskMode) {
-            Slog.w(TAG, "Skip applying hierarchy operation " + hop + " while in lock task mode");
-            return effects;
-        }
-
-        final WindowContainer wc;
-        final IBinder fragmentToken;
-        switch (type) {
-            case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
-                effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
-                break;
-            case HIERARCHY_OP_TYPE_REORDER:
-            case HIERARCHY_OP_TYPE_REPARENT:
-                wc = WindowContainer.fromBinder(hop.getContainer());
-                if (wc == null || !wc.isAttached()) {
-                    Slog.e(TAG, "Attempt to operate on detached container: " + wc);
-                    break;
-                }
-                if (syncId >= 0) {
-                    addToSyncSet(syncId, wc);
-                }
-                if (transition != null) {
-                    transition.collect(wc);
-                    if (hop.isReparent()) {
-                        if (wc.getParent() != null) {
-                            // Collect the current parent. It's visibility may change as
-                            // a result of this reparenting.
-                            transition.collect(wc.getParent());
-                        }
-                        if (hop.getNewParent() != null) {
-                            final WindowContainer parentWc =
-                                    WindowContainer.fromBinder(hop.getNewParent());
-                            if (parentWc == null) {
-                                Slog.e(TAG, "Can't resolve parent window from token");
-                                break;
-                            }
-                            transition.collect(parentWc);
-                        }
-                    }
-                }
-                effects |= sanitizeAndApplyHierarchyOp(wc, hop);
-                break;
-            case HIERARCHY_OP_TYPE_LAUNCH_TASK:
-                mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
-                        "launchTask HierarchyOp");
-                final Bundle launchOpts = hop.getLaunchOptions();
-                final int taskId = launchOpts.getInt(
-                        WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
-                launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
-                final SafeActivityOptions safeOptions =
-                        SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
-                final Integer[] starterResult = { null };
-                // startActivityFromRecents should not be called in lock.
-                mService.mH.post(() -> {
-                    try {
-                        starterResult[0] = mService.mTaskSupervisor.startActivityFromRecents(
-                                caller.mPid, caller.mUid, taskId, safeOptions);
-                    } catch (Throwable t) {
-                        starterResult[0] = ActivityManager.START_CANCELED;
-                        Slog.w(TAG, t);
-                    }
-                    synchronized (mGlobalLock) {
-                        mGlobalLock.notifyAll();
-                    }
-                });
-                while (starterResult[0] == null) {
-                    try {
-                        mGlobalLock.wait();
-                    } catch (InterruptedException ignored) {
-                    }
-                }
-                break;
-            case HIERARCHY_OP_TYPE_PENDING_INTENT:
-                String resolvedType = hop.getActivityIntent() != null
-                        ? hop.getActivityIntent().resolveTypeIfNeeded(
-                                mService.mContext.getContentResolver())
-                        : null;
-
-                Bundle options = null;
-                if (hop.getPendingIntent().isActivity()) {
-                    // Set the context display id as preferred for this activity launches, so that
-                    // it can land on caller's display. Or just brought the task to front at the
-                    // display where it was on since it has higher preference.
-                    ActivityOptions activityOptions = hop.getLaunchOptions() != null
-                            ? new ActivityOptions(hop.getLaunchOptions())
-                            : ActivityOptions.makeBasic();
-                    activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
-                    options = activityOptions.toBundle();
-                }
-
-                mService.mAmInternal.sendIntentSender(hop.getPendingIntent().getTarget(),
-                        hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
-                        hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
-                        null /* requiredPermission */, options);
-                break;
-            case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
+            }
+            case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: {
                 final TaskFragmentCreationParams taskFragmentCreationOptions =
                         hop.getTaskFragmentCreationOptions();
                 createTaskFragment(taskFragmentCreationOptions, errorCallbackToken);
                 break;
-            case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
-                wc = WindowContainer.fromBinder(hop.getContainer());
+            }
+            case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: {
+                final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
                 if (wc == null || !wc.isAttached()) {
                     Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
                     break;
@@ -699,10 +602,24 @@
                     throw new IllegalArgumentException(
                             "Can only delete organized TaskFragment, but not Task.");
                 }
+                if (isInLockTaskMode) {
+                    final ActivityRecord bottomActivity = taskFragment.getActivity(
+                            a -> !a.finishing, false /* traverseTopToBottom */);
+                    if (bottomActivity != null
+                            && mService.getLockTaskController().activityBlockedFromFinish(
+                                    bottomActivity)) {
+                        Slog.w(TAG, "Skip removing TaskFragment due in lock task mode.");
+                        sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
+                                new IllegalStateException(
+                                        "Not allow to delete task fragment in lock task mode."));
+                        break;
+                    }
+                }
                 effects |= deleteTaskFragment(taskFragment, errorCallbackToken);
                 break;
-            case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
-                fragmentToken = hop.getContainer();
+            }
+            case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
+                final IBinder fragmentToken = hop.getContainer();
                 if (!mLaunchTaskFragments.containsKey(fragmentToken)) {
                     final Throwable exception = new IllegalArgumentException(
                             "Not allowed to operate with invalid fragment token");
@@ -721,8 +638,9 @@
                             convertStartFailureToThrowable(result, activityIntent));
                 }
                 break;
-            case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
-                fragmentToken = hop.getNewParent();
+            }
+            case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
+                final IBinder fragmentToken = hop.getNewParent();
                 final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer());
                 if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) {
                     final Throwable exception = new IllegalArgumentException(
@@ -733,21 +651,9 @@
                 activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
-            case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
-                final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
-                final WindowContainer newParent = hop.getNewParent() != null
-                        ? WindowContainer.fromBinder(hop.getNewParent())
-                        : null;
-                if (oldParent == null || !oldParent.isAttached()) {
-                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
-                            + oldParent);
-                    break;
-                }
-                reparentTaskFragment(oldParent, newParent, errorCallbackToken);
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
-                break;
-            case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
-                fragmentToken = hop.getContainer();
+            }
+            case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: {
+                final IBinder fragmentToken = hop.getContainer();
                 final IBinder adjacentFragmentToken = hop.getAdjacentRoot();
                 final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken);
                 final TaskFragment tf2 = adjacentFragmentToken != null
@@ -777,6 +683,126 @@
                             adjacentParams.shouldDelaySecondaryLastActivityRemoval());
                 }
                 break;
+            }
+            default: {
+                // The other operations may change task order so they are skipped while in lock
+                // task mode. The above operations are still allowed because they don't move
+                // tasks. And it may be necessary such as clearing launch root after entering
+                // lock task mode.
+                if (isInLockTaskMode) {
+                    Slog.w(TAG, "Skip applying hierarchy operation " + hop
+                            + " while in lock task mode");
+                    return effects;
+                }
+            }
+        }
+
+        switch (type) {
+            case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: {
+                effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
+                break;
+            }
+            case HIERARCHY_OP_TYPE_REORDER:
+            case HIERARCHY_OP_TYPE_REPARENT: {
+                final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+                if (wc == null || !wc.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on detached container: " + wc);
+                    break;
+                }
+                if (syncId >= 0) {
+                    addToSyncSet(syncId, wc);
+                }
+                if (transition != null) {
+                    transition.collect(wc);
+                    if (hop.isReparent()) {
+                        if (wc.getParent() != null) {
+                            // Collect the current parent. It's visibility may change as
+                            // a result of this reparenting.
+                            transition.collect(wc.getParent());
+                        }
+                        if (hop.getNewParent() != null) {
+                            final WindowContainer parentWc =
+                                    WindowContainer.fromBinder(hop.getNewParent());
+                            if (parentWc == null) {
+                                Slog.e(TAG, "Can't resolve parent window from token");
+                                break;
+                            }
+                            transition.collect(parentWc);
+                        }
+                    }
+                }
+                effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+                break;
+            }
+            case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
+                mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
+                        "launchTask HierarchyOp");
+                final Bundle launchOpts = hop.getLaunchOptions();
+                final int taskId = launchOpts.getInt(
+                        WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
+                launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
+                final SafeActivityOptions safeOptions =
+                        SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
+                final Integer[] starterResult = {null};
+                // startActivityFromRecents should not be called in lock.
+                mService.mH.post(() -> {
+                    try {
+                        starterResult[0] = mService.mTaskSupervisor.startActivityFromRecents(
+                                caller.mPid, caller.mUid, taskId, safeOptions);
+                    } catch (Throwable t) {
+                        starterResult[0] = ActivityManager.START_CANCELED;
+                        Slog.w(TAG, t);
+                    }
+                    synchronized (mGlobalLock) {
+                        mGlobalLock.notifyAll();
+                    }
+                });
+                while (starterResult[0] == null) {
+                    try {
+                        mGlobalLock.wait();
+                    } catch (InterruptedException ignored) {
+                    }
+                }
+                break;
+            }
+            case HIERARCHY_OP_TYPE_PENDING_INTENT: {
+                String resolvedType = hop.getActivityIntent() != null
+                        ? hop.getActivityIntent().resolveTypeIfNeeded(
+                        mService.mContext.getContentResolver())
+                        : null;
+
+                Bundle options = null;
+                if (hop.getPendingIntent().isActivity()) {
+                    // Set the context display id as preferred for this activity launches, so that
+                    // it can land on caller's display. Or just brought the task to front at the
+                    // display where it was on since it has higher preference.
+                    ActivityOptions activityOptions = hop.getLaunchOptions() != null
+                            ? new ActivityOptions(hop.getLaunchOptions())
+                            : ActivityOptions.makeBasic();
+                    activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
+                    options = activityOptions.toBundle();
+                }
+
+                mService.mAmInternal.sendIntentSender(hop.getPendingIntent().getTarget(),
+                        hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
+                        hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
+                        null /* requiredPermission */, options);
+                break;
+            }
+            case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: {
+                final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
+                final WindowContainer newParent = hop.getNewParent() != null
+                        ? WindowContainer.fromBinder(hop.getNewParent())
+                        : null;
+                if (oldParent == null || !oldParent.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+                            + oldParent);
+                    break;
+                }
+                reparentTaskFragment(oldParent, newParent, errorCallbackToken);
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                break;
+            }
         }
         return effects;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8042841..db13ae2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12752,6 +12752,13 @@
         }
 
         @Override
+        public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+                @NonNull UserHandle userHandle) {
+            return DevicePolicyManagerService.this.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+                    userHandle);
+        }
+
+        @Override
         public boolean isActiveDeviceOwner(int uid) {
             return isDeviceOwner(new CallerIdentity(uid, null, null));
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
index 24c58f4..7358551 100644
--- a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
@@ -72,7 +72,7 @@
         HashMap<String, Long> installedPkgs = new HashMap<>();
         installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis());
 
-        mService.init(System.currentTimeMillis(), installedPkgs);
+        mService.init(System.currentTimeMillis(), installedPkgs, true);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 2a5bb18..df975cd 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -40,7 +40,6 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -54,13 +53,16 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
  */
-public class AccessibilityManagerServiceTest extends AndroidTestCase {
+public class AccessibilityManagerServiceTest {
     private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
     private static final int ACTION_ID = 20;
     private static final String LABEL = "label";
@@ -104,8 +106,8 @@
     private AccessibilityServiceConnection mAccessibilityServiceConnection;
     private AccessibilityManagerService mA11yms;
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
@@ -167,44 +169,48 @@
     }
 
     @SmallTest
+    @Test
     public void testRegisterSystemActionWithoutPermission() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
                 .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         try {
             mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
-            fail();
+            Assert.fail();
         } catch (SecurityException expected) {
         }
         verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
     }
 
     @SmallTest
+    @Test
     public void testRegisterSystemAction() throws Exception {
         mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
         verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
     }
 
-    @SmallTest
+    @Test
     public void testUnregisterSystemActionWithoutPermission() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
                 .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         try {
             mA11yms.unregisterSystemAction(ACTION_ID);
-            fail();
+            Assert.fail();
         } catch (SecurityException expected) {
         }
         verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
     }
 
     @SmallTest
+    @Test
     public void testUnregisterSystemAction() throws Exception {
         mA11yms.unregisterSystemAction(ACTION_ID);
         verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
     }
 
     @SmallTest
+    @Test
     public void testOnSystemActionsChanged() throws Exception {
         setupAccessibilityServiceConnection();
         mA11yms.notifySystemActionsChangedLocked(mUserState);
@@ -213,6 +219,7 @@
     }
 
     @SmallTest
+    @Test
     public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() {
         final AccessibilityUserState userState = mA11yms.mUserStates.get(
                 mA11yms.getCurrentUserIdLocked());
@@ -223,7 +230,7 @@
 
         mA11yms.onMagnificationTransitionEndedLocked(false);
 
-        assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+        Assert.assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
                 userState.getMagnificationModeLocked());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index d7daa57..a8ede13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -29,9 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
 
-import android.app.ActivityThread;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -46,7 +44,6 @@
 import com.android.server.inputmethod.InputMethodManagerService;
 import com.android.server.inputmethod.InputMethodMenuController;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,9 +62,6 @@
     private InputMethodMenuController mController;
     private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;
 
-    private IWindowManager mIWindowManager;
-    private DisplayManagerGlobal mDisplayManagerGlobal;
-
     @Before
     public void setUp() throws Exception {
         // Let the Display to be created with the DualDisplay policy.
@@ -76,12 +70,10 @@
         Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
 
         mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
-        mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
-                .Builder(mAtm, 1000, 1000).build();
 
         // Mock addWindowTokenWithOptions to create a test window token.
-        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
-        spyOn(mIWindowManager);
+        IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+        spyOn(wms);
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             IBinder clientToken = (IBinder) args[0];
@@ -91,24 +83,19 @@
                     dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
                     null /* options */);
             return dc.getImeContainer().getConfiguration();
-        }).when(mIWindowManager).attachWindowContextToDisplayArea(any(),
-                eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any());
-        mDisplayManagerGlobal = DisplayManagerGlobal.getInstance();
-        spyOn(mDisplayManagerGlobal);
+        }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
+                anyInt(), any());
+
+        mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
+                .Builder(mAtm, 1000, 1000).build();
+
+        // Mock DisplayManagerGlobal to return test display when obtaining Display instance.
         final int displayId = mSecondaryDisplay.getDisplayId();
         final Display display = mSecondaryDisplay.getDisplay();
-        doReturn(display).when(mDisplayManagerGlobal).getCompatibleDisplay(eq(displayId),
+        DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
+        spyOn(displayManagerGlobal);
+        doReturn(display).when(displayManagerGlobal).getCompatibleDisplay(eq(displayId),
                 (Resources) any());
-        Context systemUiContext = ActivityThread.currentActivityThread()
-                .getSystemUiContext(displayId);
-        spyOn(systemUiContext);
-        doReturn(display).when(systemUiContext).getDisplay();
-    }
-
-    @After
-    public void tearDown() {
-        reset(mIWindowManager);
-        reset(mDisplayManagerGlobal);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 9c04560..9639aa7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1084,6 +1084,7 @@
 
     @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
     public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() {
         // In this test, the activity's orientation isn't fixed to portrait, therefore the override
@@ -1115,6 +1116,7 @@
 
     @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
     public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() {
         // In this test, the activity's orientation isn't fixed to portrait, therefore the override
@@ -1147,6 +1149,7 @@
 
     @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
     public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() {
         setUpDisplaySizeWithApp(1000, 1200);
@@ -1175,6 +1178,52 @@
     }
 
     @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+    public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() {
+        setUpDisplaySizeWithApp(1000, 1200);
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+
+        // The per-package override forces the activity into a 3:2 aspect ratio
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                activity.getBounds().width(), 0.5);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+    public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() {
+        // In this test, the activity's orientation isn't fixed to portrait, therefore the override
+        // isn't applied.
+
+        setUpDisplaySizeWithApp(1000, 1200);
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+
+        // The per-package override forces the activity into a 3:2 aspect ratio
+        assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                activity.getBounds().height(), 0.5);
+        assertEquals(1000, activity.getBounds().width());
+    }
+
+    @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
     public void testOverrideMinAspectRatioWithoutGlobalOverride() {
         // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 1b84927..ac1fcce 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -380,6 +380,7 @@
         if (userId == UserHandle.USER_SYSTEM) {
             UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
         }
+        final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId));
         synchronized (mLock) {
             // This should be safe to add this early. Other than reportEventOrAddToQueue, every
             // other user grabs the lock before accessing
@@ -402,7 +403,7 @@
             boolean needToFlush = !pendingEvents.isEmpty();
 
             initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(),
-                    installedPackages);
+                    installedPackages, deleteObsoleteData);
             final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId);
             if (userService == null) {
                 Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId);
@@ -596,13 +597,13 @@
      * when the user is initially unlocked.
      */
     private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis,
-            HashMap<String, Long> installedPackages) {
+            HashMap<String, Long> installedPackages, boolean deleteObsoleteData) {
         final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
                 "usagestats");
         final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId,
                 usageStatsDir, this);
         try {
-            service.init(currentTimeMillis, installedPackages);
+            service.init(currentTimeMillis, installedPackages, deleteObsoleteData);
             mUserState.put(userId, service);
         } catch (Exception e) {
             if (mUserManager.isUserUnlocked(userId)) {
@@ -1165,6 +1166,10 @@
      * Called by the Binder stub.
      */
     private boolean updatePackageMappingsData() {
+        // don't update the mappings if a profile user is defined
+        if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) {
+            return true; // return true so job scheduler doesn't reschedule the job
+        }
         // fetch the installed packages outside the lock so it doesn't block package manager.
         final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
         synchronized (mLock) {
@@ -1309,6 +1314,13 @@
         }
     }
 
+    private boolean shouldDeleteObsoleteData(UserHandle userHandle) {
+        final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
+        // If a profile owner is not defined for the given user, obsolete data should be deleted
+        return dpmInternal == null
+                || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) == null;
+    }
+
     private String buildFullToken(String packageName, String token) {
         final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
         sb.append(packageName);
@@ -2532,8 +2544,12 @@
     private class MyPackageMonitor extends PackageMonitor {
         @Override
         public void onPackageRemoved(String packageName, int uid) {
-            mHandler.obtainMessage(MSG_PACKAGE_REMOVED, getChangingUserId(), 0, packageName)
-                    .sendToTarget();
+            final int changingUserId = getChangingUserId();
+            // Only remove the package's data if a profile owner is not defined for the user
+            if (shouldDeleteObsoleteData(UserHandle.of(changingUserId))) {
+                mHandler.obtainMessage(MSG_PACKAGE_REMOVED, changingUserId, 0, packageName)
+                        .sendToTarget();
+            }
             super.onPackageRemoved(packageName, uid);
         }
     }
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 36d8c85..fee4a47 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -115,8 +115,9 @@
         mSystemTimeSnapshot = System.currentTimeMillis();
     }
 
-    void init(final long currentTimeMillis, HashMap<String, Long> installedPackages) {
-        readPackageMappingsLocked(installedPackages);
+    void init(final long currentTimeMillis, HashMap<String, Long> installedPackages,
+            boolean deleteObsoleteData) {
+        readPackageMappingsLocked(installedPackages, deleteObsoleteData);
         mDatabase.init(currentTimeMillis);
         if (mDatabase.wasUpgradePerformed()) {
             mDatabase.prunePackagesDataOnUpgrade(installedPackages);
@@ -180,12 +181,13 @@
         return mDatabase.onPackageRemoved(packageName, timeRemoved);
     }
 
-    private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) {
+    private void readPackageMappingsLocked(HashMap<String, Long> installedPackages,
+            boolean deleteObsoleteData) {
         mDatabase.readMappingsLocked();
         // Package mappings for the system user are updated after 24 hours via a job scheduled by
         // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally,
         // this makes user service initialization a little quicker on subsequent boots.
-        if (mUserId != UserHandle.USER_SYSTEM) {
+        if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) {
             updatePackageMappingsLocked(installedPackages);
         }
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 54d3af5..4d2e007 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3893,6 +3893,30 @@
     public static final String KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL =
             "enabled_4g_opportunistic_network_scan_bool";
 
+  /**
+   * Only relevant when the device supports opportunistic networks but does not support
+   * simultaneuous 5G+5G. Controls how long, in milliseconds, to wait before opportunistic network
+   * goes out of service before switching the 5G capability back to primary stack. The idea of
+   * waiting a few seconds is to minimize the calling of the expensive capability switching
+   * operation in the case where CBRS goes back into service shortly after going out of it.
+   *
+   * @hide
+   */
+  public static final String KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG =
+            "time_to_switch_back_to_primary_if_opportunistic_oos_long";
+
+  /**
+   * Only relevant when the device supports opportunistic networks but does not support
+   * simultaneuous 5G+5G. Controls how long, in milliseconds, after 5G capability has switched back
+   * to primary stack due to opportunistic network being OOS. The idea is to minimizing the
+   * 'ping-ponging' effect where device is constantly witching capability back and forth between
+   * primary and opportunistic stack.
+   *
+   * @hide
+   */
+  public static final String KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG
+          = "opportunistic_time_to_scan_after_capability_switch_to_primary_long";
+
     /**
      * Indicates zero or more emergency number prefix(es), because some carrier requires
      * if users dial an emergency number address with a specific prefix, the combination of the
@@ -5766,6 +5790,10 @@
         /* Default value is 2 seconds. */
         sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_5G_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 2000);
         sDefaults.putBoolean(KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL, true);
+        sDefaults.putInt(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000);
+        sDefaults.putInt(
+                KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG,
+                120000);
         sDefaults.putAll(Gps.getDefaults());
         sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
                 new int[] {