Merge "More improve IME transition during task switch" into sc-v2-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index d684e4b..ec8dce0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20454,7 +20454,7 @@
     method public String getProperty(String);
     method public int getRingerMode();
     method @Deprecated public int getRouting(int);
-    method @Nullable public android.media.Spatializer getSpatializer();
+    method @NonNull public android.media.Spatializer getSpatializer();
     method public int getStreamMaxVolume(int);
     method public int getStreamMinVolume(int);
     method public int getStreamVolume(int);
@@ -23854,9 +23854,13 @@
   public class Spatializer {
     method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
     method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat);
+    method public int getImmersiveAudioLevel();
     method public boolean isAvailable();
     method public boolean isEnabled();
     method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+    field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1
+    field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0
+    field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff
   }
 
   public static interface Spatializer.OnSpatializerStateChangedListener {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index df46822..2ecf088 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -425,6 +425,7 @@
 
   public class DevicePolicyManager {
     method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
+    method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void clearOrganizationId();
     method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
     method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 77bcef3..be702c2 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -46,7 +46,7 @@
  */
 @SystemService(Context.STATUS_BAR_SERVICE)
 public class StatusBarManager {
-
+    // LINT.IfChange
     /** @hide */
     public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND;
     /** @hide */
@@ -144,6 +144,7 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Disable2Flags {}
+    // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt)
 
     /**
      * Default disable flags for setup
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 123046e..b570ae6 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -562,7 +562,7 @@
             }
             if (returnDefault) {
                 Bitmap defaultWallpaper = mDefaultWallpaper;
-                if (defaultWallpaper == null) {
+                if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
                     defaultWallpaper = getDefaultWallpaper(context, which);
                     synchronized (this) {
                         mDefaultWallpaper = defaultWallpaper;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e04ad3..12444ab 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -13740,6 +13740,23 @@
     }
 
     /**
+     * Clears organization ID set by the DPC and resets the precomputed enrollment specific ID.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void clearOrganizationId() {
+        if (mService == null) {
+            return;
+        }
+        try {
+            mService.clearOrganizationIdForUser(myUserId());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Creates and provisions a managed profile and sets the
      * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
      * owner.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b6c48a1..b1364b5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -377,6 +377,7 @@
 
     void setOrganizationColor(in ComponentName admin, in int color);
     void setOrganizationColorForUser(in int color, in int userId);
+    void clearOrganizationIdForUser(int userHandle);
     int getOrganizationColor(in ComponentName admin);
     int getOrganizationColorForUser(int userHandle);
 
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 063ba11..2e94dd1 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -143,7 +143,7 @@
     public ComponentName provider;
 
     /**
-     * The default height of the widget when added to a host, in dp. The widget will get
+     * The default height of the widget when added to a host, in px. The widget will get
      * at least this width, and will often be given more, depending on the host.
      *
      * <p>This field corresponds to the <code>android:minWidth</code> attribute in
@@ -152,7 +152,7 @@
     public int minWidth;
 
     /**
-     * The default height of the widget when added to a host, in dp. The widget will get
+     * The default height of the widget when added to a host, in px. The widget will get
      * at least this height, and will often be given more, depending on the host.
      *
      * <p>This field corresponds to the <code>android:minHeight</code> attribute in
@@ -161,7 +161,7 @@
     public int minHeight;
 
     /**
-     * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+     * Minimum width (in px) which the widget can be resized to. This field has no effect if it
      * is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in
@@ -170,7 +170,7 @@
     public int minResizeWidth;
 
     /**
-     * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+     * Minimum height (in px) which the widget can be resized to. This field has no effect if it
      * is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in
@@ -179,7 +179,7 @@
     public int minResizeHeight;
 
     /**
-     * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+     * Maximum width (in px) which the widget can be resized to. This field has no effect if it is
      * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
@@ -189,7 +189,7 @@
     public int maxResizeWidth;
 
     /**
-     * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+     * Maximum height (in px) which the widget can be resized to. This field has no effect if it is
      * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 0b92b93..874e3f4 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -47,6 +47,8 @@
 public class AccessibilityShortcutChooserActivity extends Activity {
     @ShortcutType
     private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
+    private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
+            "accessibility_shortcut_menu_mode";
     private final List<AccessibilityTarget> mTargets = new ArrayList<>();
     private AlertDialog mMenuDialog;
     private AlertDialog mPermissionDialog;
@@ -66,14 +68,30 @@
         mMenuDialog = createMenuDialog();
         mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
         mMenuDialog.show();
+
+        if (savedInstanceState != null) {
+            final int restoreShortcutMenuMode =
+                    savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE,
+                            ShortcutMenuMode.LAUNCH);
+            if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) {
+                onEditButtonClicked();
+            }
+        }
     }
 
     @Override
     protected void onDestroy() {
+        mMenuDialog.setOnDismissListener(null);
         mMenuDialog.dismiss();
         super.onDestroy();
     }
 
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode());
+    }
+
     private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
         final AccessibilityTarget target = mTargets.get(position);
         target.onSelected();
diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
index 4ce6f60..3eb9804 100644
--- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
+++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
@@ -17,19 +17,27 @@
 package com.android.internal.notification;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 
+import com.android.internal.R;
+
+/**
+ * This class provides methods to create intents for NotificationAccessConfirmationActivity.
+ */
 public final class NotificationAccessConfirmationActivityContract {
-    private static final ComponentName COMPONENT_NAME = new ComponentName(
-            "com.android.settings",
-            "com.android.settings.notification.NotificationAccessConfirmationActivity");
     public static final String EXTRA_USER_ID = "user_id";
     public static final String EXTRA_COMPONENT_NAME = "component_name";
     public static final String EXTRA_PACKAGE_TITLE = "package_title";
 
-    public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) {
+    /**
+     * Creates a launcher intent for NotificationAccessConfirmationActivity.
+     */
+    public static Intent launcherIntent(Context context, int userId, ComponentName component,
+            String packageTitle) {
         return new Intent()
-                .setComponent(COMPONENT_NAME)
+                .setComponent(ComponentName.unflattenFromString(context.getString(
+                        R.string.config_notificationAccessConfirmationActivity)))
                 .putExtra(EXTRA_USER_ID, userId)
                 .putExtra(EXTRA_COMPONENT_NAME, component)
                 .putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505c..cd1bbb6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.widget;
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -1272,6 +1273,14 @@
     }
 
     /**
+     * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED.
+     */
+    public boolean isCredentialsDisabledForUser(int userId) {
+        return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId)
+                == PASSWORD_QUALITY_MANAGED;
+    }
+
+    /**
      * @see StrongAuthTracker#isTrustAllowedForUser
      */
     public boolean isTrustAllowedForUser(int userId) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e53d379..63d61fc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5876,7 +5876,6 @@
                   android:excludeFromRecents="true"
                   android:documentLaunchMode="never"
                   android:relinquishTaskIdentity="true"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                   android:process=":ui"
                   android:visibleToInstantApps="true">
             <intent-filter>
diff --git a/core/res/res/layout/accessibility_shortcut_chooser_item.xml b/core/res/res/layout/accessibility_shortcut_chooser_item.xml
index 7cca129..4d7946b 100644
--- a/core/res/res/layout/accessibility_shortcut_chooser_item.xml
+++ b/core/res/res/layout/accessibility_shortcut_chooser_item.xml
@@ -39,15 +39,20 @@
         android:layout_height="48dp"
         android:scaleType="fitCenter"/>
 
-    <TextView
-        android:id="@+id/accessibility_shortcut_target_label"
+    <LinearLayout
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="16dp"
-        android:layout_weight="1"
-        android:textSize="20sp"
-        android:textColor="?attr/textColorPrimary"
-        android:fontFamily="sans-serif-medium"/>
+        android:layout_weight="1">
+
+        <TextView
+            android:id="@+id/accessibility_shortcut_target_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20sp"
+            android:textColor="?attr/textColorPrimary"
+            android:fontFamily="sans-serif-medium"/>
+    </LinearLayout>
 
     <TextView
         android:id="@+id/accessibility_shortcut_target_status"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d5f4cad..a48ffc7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5174,6 +5174,12 @@
         <item>@array/config_secondaryBuiltInDisplayWaterfallCutout</item>
     </array>
 
+    <!-- The component name of the activity for the companion-device-manager notification access
+     confirmation. -->
+    <string name="config_notificationAccessConfirmationActivity" translatable="false">
+        com.android.settings/com.android.settings.notification.NotificationAccessConfirmationActivity
+    </string>
+
     <!-- Whether the airplane mode should be reset when device boots in non-safemode after exiting
          from safemode.
          This flag should be enabled only when the product does not have any UI to toggle airplane
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7fe73e5..51cc7db 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2221,6 +2221,7 @@
   <java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" />
   <java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
   <java-symbol type="bool" name="config_autoResetAirplaneMode" />
+  <java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0d7225b..119c9391 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1207,6 +1207,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-779535710": {
+      "message": "Transition %d: Set %s as transient-launch",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-775004869": {
       "message": "Not a match: %s",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 9d65d28..05ebbba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -422,14 +422,6 @@
         }
     }
 
-    @Override
-    public void setExpandedContentAlpha(float alpha) {
-        if (mExpandedView != null) {
-            mExpandedView.setAlpha(alpha);
-            mExpandedView.setTaskViewAlpha(alpha);
-        }
-    }
-
     /**
      * Set visibility of bubble in the expanded state.
      *
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 9dafefe..c126f32 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
@@ -69,6 +69,7 @@
 import android.util.SparseSetArray;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.window.WindowContainerTransaction;
 
@@ -144,7 +145,6 @@
 
     private BubbleLogger mLogger;
     private BubbleData mBubbleData;
-    private View mBubbleScrim;
     @Nullable private BubbleStackView mStackView;
     private BubbleIconFactory mBubbleIconFactory;
     private BubblePositioner mBubblePositioner;
@@ -189,6 +189,9 @@
     /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
     private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
 
+    /** Saved insets, used to detect WindowInset changes. */
+    private WindowInsets mWindowInsets;
+
     private boolean mInflateSynchronously;
 
     /** True when user is in status bar unlock shade. */
@@ -629,8 +632,11 @@
             mBubbleData.getOverflow().initialize(this);
             mWindowManager.addView(mStackView, mWmLayoutParams);
             mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
-                mBubblePositioner.update();
-                mStackView.onDisplaySizeChanged();
+                if (!windowInsets.equals(mWindowInsets)) {
+                    mWindowInsets = windowInsets;
+                    mBubblePositioner.update();
+                    mStackView.onDisplaySizeChanged();
+                }
                 return windowInsets;
             });
         } catch (IllegalStateException e) {
@@ -1372,8 +1378,9 @@
     private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+            mBubblePositioner.setImeVisible(imeVisible, imeHeight);
             if (mStackView != null) {
-                mStackView.onImeVisibilityChanged(imeVisible, imeHeight);
+                mStackView.animateForIme(imeVisible);
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 7d7bfb2..a87aad4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -398,6 +398,7 @@
         updatePointerView();
     }
 
+    /** Updates the size and visuals of the pointer. **/
     private void updatePointerView() {
         LayoutParams lp = (LayoutParams) mPointerView.getLayoutParams();
         if (mCurrentPointer == mLeftPointer || mCurrentPointer == mRightPointer) {
@@ -524,9 +525,8 @@
         if (mTaskView != null) {
             mTaskView.setAlpha(alpha);
         }
-        if (mManageButton != null && mManageButton.getVisibility() == View.VISIBLE) {
-            mManageButton.setAlpha(alpha);
-        }
+        mPointerView.setAlpha(alpha);
+        setAlpha(alpha);
     }
 
     /**
@@ -545,6 +545,7 @@
         mIsContentVisible = visibility;
         if (mTaskView != null && !mIsAlphaAnimating) {
             mTaskView.setAlpha(visibility ? 1f : 0f);
+            mPointerView.setAlpha(visibility ? 1f : 0f);
         }
     }
 
@@ -689,7 +690,7 @@
      *                       the bubble if showing vertically.
      * @param onLeft whether the stack was on the left side of the screen when expanded.
      */
-    public void setPointerPosition(float bubblePosition, boolean onLeft) {
+    public void setPointerPosition(float bubblePosition, boolean onLeft, boolean animate) {
         // Pointer gets drawn in the padding
         final boolean showVertically = mPositioner.showBubblesVertically();
         final float paddingLeft = (showVertically && onLeft)
@@ -710,6 +711,8 @@
                 : pointerPosition;
         // Post because we need the width of the view
         post(() -> {
+            mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
+            updatePointerView();
             float pointerY;
             float pointerX;
             if (showVertically) {
@@ -721,11 +724,13 @@
                 pointerY = mPointerOverlap;
                 pointerX = bubbleCenter - (mPointerWidth / 2f);
             }
-            mPointerView.setTranslationY(pointerY);
-            mPointerView.setTranslationX(pointerX);
-            mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
-            updatePointerView();
-            mPointerView.setVisibility(VISIBLE);
+            if (animate) {
+                mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
+            } else {
+                mPointerView.setTranslationY(pointerY);
+                mPointerView.setTranslationX(pointerX);
+                mPointerView.setVisibility(VISIBLE);
+            }
         });
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 705a12a..0c3a6b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -154,10 +154,6 @@
         return dotPath
     }
 
-    override fun setExpandedContentAlpha(alpha: Float) {
-        expandedView?.alpha = alpha
-    }
-
     override fun setTaskViewVisibility(visible: Boolean) {
         // Overflow does not have a TaskView.
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 306224b..127d5a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -70,13 +70,16 @@
 
     private Context mContext;
     private WindowManager mWindowManager;
-    private Rect mPositionRect;
     private Rect mScreenRect;
     private @Surface.Rotation int mRotation = Surface.ROTATION_0;
     private Insets mInsets;
+    private boolean mImeVisible;
+    private int mImeHeight;
+    private boolean mIsLargeScreen;
+
+    private Rect mPositionRect;
     private int mDefaultMaxBubbles;
     private int mMaxBubbles;
-
     private int mBubbleSize;
     private int mSpacingBetweenBubbles;
 
@@ -98,7 +101,6 @@
     private PointF mRestingStackPosition;
     private int[] mPaddings = new int[4];
 
-    private boolean mIsLargeScreen;
     private boolean mShowingInTaskbar;
     private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
     private int mTaskbarIconSize;
@@ -302,6 +304,17 @@
         return mMaxBubbles;
     }
 
+    /** The height for the IME if it's visible. **/
+    public int getImeHeight() {
+        return mImeVisible ? mImeHeight : 0;
+    }
+
+    /** Sets whether the IME is visible. **/
+    public void setImeVisible(boolean visible, int height) {
+        mImeVisible = visible;
+        mImeHeight = height;
+    }
+
     /**
      * Calculates the padding for the bubble expanded view.
      *
@@ -357,7 +370,7 @@
     }
 
     /** Gets the y position of the expanded view if it was top-aligned. */
-    private float getExpandedViewYTopAligned() {
+    public float getExpandedViewYTopAligned() {
         final int top = getAvailableRect().top;
         if (showBubblesVertically()) {
             return top - mPointerWidth + mExpandedViewPadding;
@@ -366,10 +379,6 @@
         }
     }
 
-    public float getExpandedBubblesY() {
-        return getAvailableRect().top + mExpandedViewPadding;
-    }
-
     /**
      * Calculate the maximum height the expanded view can be depending on where it's placed on
      * the screen and the size of the elements around it (e.g. padding, pointer, manage button).
@@ -464,18 +473,21 @@
                 : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
     }
 
+    private int getExpandedStackSize(int numberOfBubbles) {
+        return (numberOfBubbles * mBubbleSize)
+                + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
+    }
+
     /**
      * Returns the position of the bubble on-screen when the stack is expanded.
      *
      * @param index the index of the bubble in the stack.
-     * @param numberOfBubbles the total number of bubbles in the stack.
-     * @param onLeftEdge whether the stack would rest on the left edge of the screen when collapsed.
-     * @return the x, y position of the bubble on-screen when the stack is expanded.
+     * @param state state information about the stack to help with calculations.
+     * @return the position of the bubble on-screen when the stack is expanded.
      */
-    public PointF getExpandedBubbleXY(int index, int numberOfBubbles, boolean onLeftEdge) {
+    public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
         final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles);
-        final float expandedStackSize = (numberOfBubbles * mBubbleSize)
-                + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
+        final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
         final float centerPosition = showBubblesVertically()
                 ? mPositionRect.centerY()
                 : mPositionRect.centerX();
@@ -491,17 +503,66 @@
             int right = mIsLargeScreen
                     ? mPositionRect.right - mExpandedViewLargeScreenInset + mExpandedViewPadding
                     : mPositionRect.right - mBubbleSize;
-            x = onLeftEdge
+            x = state.onLeft
                     ? left
                     : right;
         } else {
             y = mPositionRect.top + mExpandedViewPadding;
             x = rowStart + positionInRow;
         }
+
+        if (showBubblesVertically() && mImeVisible) {
+            return new PointF(x, getExpandedBubbleYForIme(index, state.numberOfBubbles));
+        }
         return new PointF(x, y);
     }
 
     /**
+     * Returns the position of the bubble on-screen when the stack is expanded and the IME
+     * is showing.
+     *
+     * @param index the index of the bubble in the stack.
+     * @param numberOfBubbles the total number of bubbles in the stack.
+     * @return y position of the bubble on-screen when the stack is expanded.
+     */
+    private float getExpandedBubbleYForIme(int index, int numberOfBubbles) {
+        final float top = getAvailableRect().top + mExpandedViewPadding;
+        if (!showBubblesVertically()) {
+            // Showing horizontally: align to top
+            return top;
+        }
+
+        // Showing vertically: might need to translate the bubbles above the IME.
+        // Subtract spacing here to provide a margin between top of IME and bottom of bubble row.
+        final float bottomInset = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+        final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
+        final float centerPosition = showBubblesVertically()
+                ? mPositionRect.centerY()
+                : mPositionRect.centerX();
+        final float rowBottom = centerPosition + (expandedStackSize / 2f);
+        final float rowTop = centerPosition - (expandedStackSize / 2f);
+        float rowTopForIme = rowTop;
+        if (rowBottom > bottomInset) {
+            // We overlap with IME, must shift the bubbles
+            float translationY = rowBottom - bottomInset;
+            rowTopForIme = Math.max(rowTop - translationY, top);
+            if (rowTop - translationY < top) {
+                // Even if we shift the bubbles, they will still overlap with the IME.
+                // Hide the overflow for a lil more space:
+                final float expandedStackSizeNoO = getExpandedStackSize(numberOfBubbles - 1);
+                final float centerPositionNoO = showBubblesVertically()
+                        ? mPositionRect.centerY()
+                        : mPositionRect.centerX();
+                final float rowBottomNoO = centerPositionNoO + (expandedStackSizeNoO / 2f);
+                final float rowTopNoO = centerPositionNoO - (expandedStackSizeNoO / 2f);
+                translationY = rowBottomNoO - bottomInset;
+                rowTopForIme = rowTopNoO - translationY;
+            }
+        }
+        return rowTopForIme + (index * (mBubbleSize + mSpacingBetweenBubbles));
+    }
+
+    /**
      * @return the width of the bubble flyout (message originating from the bubble).
      */
     public float getMaxFlyoutSize() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 5bc6128..9d0dd3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -28,6 +28,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.content.ContentResolver;
@@ -188,6 +190,7 @@
     };
     private final BubbleController mBubbleController;
     private final BubbleData mBubbleData;
+    private StackViewState mStackViewState = new StackViewState();
 
     private final ValueAnimator mDismissBubbleAnimator;
 
@@ -246,7 +249,6 @@
     private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
     private int mCornerRadius;
-    private int mImeOffset;
     @Nullable private BubbleViewProvider mExpandedBubble;
     private boolean mIsExpanded;
 
@@ -757,7 +759,6 @@
         mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
         mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
-        mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
 
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -779,7 +780,7 @@
                 this::animateShadows /* onStackAnimationFinished */, mPositioner);
 
         mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
-                onBubbleAnimatedOut);
+                onBubbleAnimatedOut, this);
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
         // Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
@@ -890,7 +891,7 @@
                         // Re-draw bubble row and pointer for new orientation.
                         beforeExpandedViewAnimation();
                         updateOverflowVisibility();
-                        updatePointerPosition();
+                        updatePointerPosition(false /* forIme */);
                         mExpandedAnimationController.expandFromStack(() -> {
                             afterExpandedViewAnimation();
                         } /* after */);
@@ -968,7 +969,8 @@
         });
         mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
             if (mExpandedBubble != null) {
-                mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue());
+                mExpandedBubble.getExpandedView().setTaskViewAlpha(
+                        (float) valueAnimator.getAnimatedValue());
             }
         });
 
@@ -1558,7 +1560,7 @@
                 } else {
                     bubble.cleanupViews();
                 }
-                updatePointerPosition();
+                updatePointerPosition(false /* forIme */);
                 updateExpandedView();
                 logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
                 return;
@@ -1599,7 +1601,7 @@
                     .map(b -> b.getIconView()).collect(Collectors.toList());
             mStackAnimationController.animateReorder(bubbleViews, reorder);
         }
-        updatePointerPosition();
+        updatePointerPosition(false /* forIme */);
     }
 
     /**
@@ -1670,7 +1672,6 @@
     private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
         final BubbleViewProvider previouslySelected = mExpandedBubble;
         mExpandedBubble = bubbleToSelect;
-        updatePointerPosition();
 
         if (mIsExpanded) {
             hideCurrentInputMethod();
@@ -1762,6 +1763,7 @@
      * not.
      */
     void hideCurrentInputMethod() {
+        mPositioner.setImeVisible(false, 0);
         mBubbleController.hideCurrentInputMethod();
     }
 
@@ -1864,7 +1866,7 @@
         updateBadges(false /* setBadgeForCollapsedStack */);
         mBubbleContainer.setActiveController(mExpandedAnimationController);
         updateOverflowVisibility();
-        updatePointerPosition();
+        updatePointerPosition(false /* forIme */);
         mExpandedAnimationController.expandFromStack(() -> {
             if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
                 maybeShowManageEdu();
@@ -1876,8 +1878,7 @@
         } else {
             index = getBubbleIndex(mExpandedBubble);
         }
-        PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleContainer.getChildCount(),
-                mStackOnLeftOrWillBe);
+        PointF p = mPositioner.getExpandedBubbleXY(index, getState());
         final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
                 mPositioner.showBubblesVertically() ? p.y : p.x);
         mExpandedViewContainer.setTranslationX(0f);
@@ -1928,7 +1929,7 @@
         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
 
         if (mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.setExpandedContentAlpha(0f);
+            mExpandedBubble.getExpandedView().setTaskViewAlpha(0f);
 
             // We'll be starting the alpha animation after a slight delay, so set this flag early
             // here.
@@ -2011,8 +2012,7 @@
             index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
         }
         // Value the bubble is animating from (back into the stack).
-        final PointF p = mPositioner.getExpandedBubbleXY(index,
-                mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe);
+        final PointF p = mPositioner.getExpandedBubbleXY(index, getState());
         if (mPositioner.showBubblesVertically()) {
             float pivotX;
             float pivotY = p.y + mBubbleSize / 2f;
@@ -2109,7 +2109,7 @@
         PointF p = mPositioner.getExpandedBubbleXY(isOverflow
                         ? mBubbleContainer.getChildCount() - 1
                         : mBubbleData.getBubbles().indexOf(mExpandedBubble),
-                    mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe);
+                getState());
         mExpandedViewContainer.setAlpha(1f);
         mExpandedViewContainer.setVisibility(View.VISIBLE);
 
@@ -2187,9 +2187,20 @@
         }
     }
 
-    /** Moves the bubbles out of the way if they're going to be over the keyboard. */
-    public void onImeVisibilityChanged(boolean visible, int height) {
-        mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
+    /**
+     * Updates the stack based for IME changes. When collapsed it'll move the stack if it
+     * overlaps where they IME would be. When expanded it'll shift the expanded bubbles
+     * if they might overlap with the IME (this only happens for large screens).
+     */
+    public void animateForIme(boolean visible) {
+        if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
+            // This will update the animation so the bubbles move to position for the IME
+            mExpandedAnimationController.expandFromStack(() -> {
+                updatePointerPosition(false /* forIme */);
+                afterExpandedViewAnimation();
+            } /* after */);
+            return;
+        }
 
         if (!mIsExpanded && getBubbleCount() > 0) {
             final float stackDestinationY =
@@ -2208,9 +2219,20 @@
                                 FLYOUT_IME_ANIMATION_SPRING_CONFIG)
                         .start();
             }
-        } else if (mIsExpanded && mExpandedBubble != null
-                && mExpandedBubble.getExpandedView() != null) {
+        } else if (mPositioner.showBubblesVertically() && mIsExpanded
+                && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             mExpandedBubble.getExpandedView().setImeVisible(visible);
+            List<Animator> animList = new ArrayList();
+            for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+                View child = mBubbleContainer.getChildAt(i);
+                float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
+                ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
+                animList.add(anim);
+            }
+            updatePointerPosition(true /* forIme */);
+            AnimatorSet set = new AnimatorSet();
+            set.playTogether(animList);
+            set.start();
         }
     }
 
@@ -2514,7 +2536,7 @@
             // Account for the IME in the touchable region so that the touchable region of the
             // Bubble window doesn't obscure the IME. The touchable region affects which areas
             // of the screen can be excluded by lower windows (IME is just above the embedded task)
-            outRect.bottom -= (int) mStackAnimationController.getImeHeight();
+            outRect.bottom -= mPositioner.getImeHeight();
         }
 
         if (mFlyout.getVisibility() == View.VISIBLE) {
@@ -2773,13 +2795,13 @@
         }
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
-                    mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe);
+                    getState());
             mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
                     mPositioner.showBubblesVertically() ? p.y : p.x));
             mExpandedViewContainer.setTranslationX(0f);
             mExpandedBubble.getExpandedView().updateView(
                     mExpandedViewContainer.getLocationOnScreen());
-            updatePointerPosition();
+            updatePointerPosition(false /* forIme */);
         }
 
         mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
@@ -2850,7 +2872,13 @@
         }
     }
 
-    private void updatePointerPosition() {
+    /**
+     * Updates the position of the pointer based on the expanded bubble.
+     *
+     * @param forIme whether the position is being updated due to the ime appearing, in this case
+     *               the pointer is animated to the location.
+     */
+    private void updatePointerPosition(boolean forIme) {
         if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
             return;
         }
@@ -2858,13 +2886,12 @@
         if (index == -1) {
             return;
         }
-        PointF bubblePosition = mPositioner.getExpandedBubbleXY(index,
-                mBubbleContainer.getChildCount(),
-                mStackOnLeftOrWillBe);
-        mExpandedBubble.getExpandedView().setPointerPosition(mPositioner.showBubblesVertically()
-                ? bubblePosition.y
-                : bubblePosition.x,
-                mStackOnLeftOrWillBe);
+        PointF position = mPositioner.getExpandedBubbleXY(index, getState());
+        float bubblePosition = mPositioner.showBubblesVertically()
+                ? position.y
+                : position.x;
+        mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition,
+                mStackOnLeftOrWillBe, forIme /* animate */);
     }
 
     /**
@@ -2947,6 +2974,26 @@
         return bubbles;
     }
 
+    /** @return the current stack state. */
+    public StackViewState getState() {
+        mStackViewState.numberOfBubbles = mBubbleContainer.getChildCount();
+        mStackViewState.selectedIndex = getBubbleIndex(mExpandedBubble);
+        mStackViewState.onLeft = mStackOnLeftOrWillBe;
+        return mStackViewState;
+    }
+
+    /**
+     * Holds some commonly queried information about the stack.
+     */
+    public static class StackViewState {
+        // Number of bubbles (including the overflow itself) in the stack.
+        public int numberOfBubbles;
+        // The selected index if the stack is expanded.
+        public int selectedIndex;
+        // Whether the stack is resting on the left or right side of the screen when collapsed.
+        public boolean onLeft;
+    }
+
     /**
      * Representation of stack position that uses relative properties rather than absolute
      * coordinates. This is used to maintain similar stack positions across configuration changes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index 38b3ba9..7e55282 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -29,12 +29,6 @@
     @Nullable BubbleExpandedView getExpandedView();
 
     /**
-     * Sets the alpha of the expanded view content. This will be applied to both the expanded view
-     * container itself (the manage button, etc.) as well as the TaskView within it.
-     */
-    void setExpandedContentAlpha(float alpha);
-
-    /**
      * Sets whether the contents of the bubble's TaskView should be visible.
      */
     void setTaskViewVisibility(boolean visible);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index c32be98..f0f78748 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.graphics.Path;
 import android.graphics.PointF;
-import android.graphics.Rect;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -33,6 +32,7 @@
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import com.google.android.collect.Sets;
@@ -124,12 +124,15 @@
 
     private BubblePositioner mPositioner;
 
+    private BubbleStackView mBubbleStackView;
+
     public ExpandedAnimationController(BubblePositioner positioner,
-            Runnable onBubbleAnimatedOutAction) {
+            Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) {
         mPositioner = positioner;
         updateResources();
         mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
         mCollapsePoint = mPositioner.getDefaultStartPosition();
+        mBubbleStackView = stackView;
     }
 
     /**
@@ -239,10 +242,7 @@
             final Path path = new Path();
             path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
 
-            boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
-            final PointF p = mPositioner.getExpandedBubbleXY(index,
-                    mLayout.getChildCount(),
-                    onLeft);
+            final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
             if (expanding) {
                 // If we're expanding, first draw a line from the bubble's current position to where
                 // it'll end up
@@ -364,7 +364,7 @@
             bubbleView.setTranslationY(y);
         }
 
-        final float expandedY = mPositioner.getExpandedBubblesY();
+        final float expandedY = mPositioner.getExpandedViewYTopAligned();
         final boolean draggedOutEnough =
                 y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
         if (draggedOutEnough != mBubbleDraggedOutEnough) {
@@ -410,8 +410,7 @@
             return;
         }
         final int index = mLayout.indexOfChild(bubbleView);
-        final PointF p = mPositioner.getExpandedBubbleXY(index, mLayout.getChildCount(),
-                mPositioner.isStackOnLeft(mCollapsePoint));
+        final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
         animationForChildAtIndex(index)
                 .position(p.x, p.y)
                 .withPositionStartVelocities(velX, velY)
@@ -429,16 +428,6 @@
         updateBubblePositions();
     }
 
-    /**
-     * Animates the bubbles to the y position. Used in response to IME showing.
-     */
-    public void updateYPosition(Runnable after) {
-        if (mLayout == null) return;
-        animationsForChildrenFromIndex(
-                0, (i, anim) -> anim.translationY(mPositioner.getExpandedBubblesY()))
-                .startAll(after);
-    }
-
     /** Description of current animation controller state. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("ExpandedAnimationController state:");
@@ -496,36 +485,33 @@
             startOrUpdatePathAnimation(false /* expanding */);
         } else {
             boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
-            final PointF p = mPositioner.getExpandedBubbleXY(index,
-                    mLayout.getChildCount(),
-                    onLeft);
+            final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
             if (mPositioner.showBubblesVertically()) {
                 child.setTranslationY(p.y);
             } else {
                 child.setTranslationX(p.x);
             }
-            if (!mPreparingToCollapse) {
-                // Only animate if we're not collapsing as that animation will handle placing the
+
+            if (mPreparingToCollapse) {
+                // Don't animate if we're collapsing, as that animation will handle placing the
                 // new bubble in the stacked position.
-                if (mPositioner.showBubblesVertically()) {
-                    Rect availableRect = mPositioner.getAvailableRect();
-                    float fromX = onLeft
-                            ? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
-                            : availableRect.right + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
-                    animationForChild(child)
-                            .translationX(fromX, p.y)
-                            .start();
-                } else {
-                    // Only animate if we're not collapsing as that animation will handle placing
-                    // the new bubble in the stacked position.
-                    float fromY = mPositioner.getExpandedBubblesY() - mBubbleSizePx
-                            * ANIMATE_TRANSLATION_FACTOR;
-                    animationForChild(child)
-                            .translationY(fromY, p.y)
-                            .start();
-                }
-                updateBubblePositions();
+                return;
             }
+
+            if (mPositioner.showBubblesVertically()) {
+                float fromX = onLeft
+                        ? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
+                        : p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
+                animationForChild(child)
+                        .translationX(fromX, p.y)
+                        .start();
+            } else {
+                float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
+                animationForChild(child)
+                        .translationY(fromY, p.y)
+                        .start();
+            }
+            updateBubblePositions();
         }
     }
 
@@ -572,7 +558,6 @@
         if (mAnimatingExpand || mAnimatingCollapse) {
             return;
         }
-        boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
         for (int i = 0; i < mLayout.getChildCount(); i++) {
             final View bubble = mLayout.getChildAt(i);
 
@@ -582,7 +567,7 @@
                 return;
             }
 
-            final PointF p = mPositioner.getExpandedBubbleXY(i, mLayout.getChildCount(), onLeft);
+            final PointF p = mPositioner.getExpandedBubbleXY(i, mBubbleStackView.getState());
             animationForChild(bubble)
                     .translationX(p.x)
                     .translationY(p.y)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 9a08190..60b6433 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -127,9 +127,6 @@
     /** Whether or not the stack's start position has been set. */
     private boolean mStackMovedToStartPosition = false;
 
-    /** The height of the most recently visible IME. */
-    private float mImeHeight = 0f;
-
     /**
      * The Y position of the stack before the IME became visible, or {@link Float#MIN_VALUE} if the
      * IME is not visible or the user moved the stack since the IME became visible.
@@ -173,7 +170,7 @@
      */
     private boolean mSpringToTouchOnNextMotionEvent = false;
 
-    /** Horizontal offset of bubbles in the stack. */
+    /** Offset of bubbles in the stack (i.e. how much they overlap). */
     private float mStackOffset;
     /** Offset between stack y and animation y for bubble swap. */
     private float mSwapAnimationOffset;
@@ -521,16 +518,6 @@
         removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
     }
 
-    /** Save the current IME height so that we know where the stack bounds should be. */
-    public void setImeHeight(int imeHeight) {
-        mImeHeight = imeHeight;
-    }
-
-    /** Returns the current IME height that the stack is offset by. */
-    public float getImeHeight() {
-        return mImeHeight;
-    }
-
     /**
      * Animates the stack either away from the newly visible IME, or back to its original position
      * due to the IME going away.
@@ -589,11 +576,14 @@
      */
     public RectF getAllowableStackPositionRegion() {
         final RectF allowableRegion = new RectF(mPositioner.getAvailableRect());
+        final int imeHeight = mPositioner.getImeHeight();
+        final float bottomPadding = getBubbleCount() > 1
+                ? mBubblePaddingTop + mStackOffset
+                : mBubblePaddingTop;
         allowableRegion.left -= mBubbleOffscreen;
         allowableRegion.top += mBubblePaddingTop;
         allowableRegion.right += mBubbleOffscreen - mBubbleSize;
-        allowableRegion.bottom -= mBubblePaddingTop + mBubbleSize
-                + (mImeHeight != UNSET ? mImeHeight + mBubblePaddingTop : 0f);
+        allowableRegion.bottom -= imeHeight + bottomPadding + mBubbleSize;
         return allowableRegion;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 9732a88..2b9bdce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,10 +16,12 @@
 
 package com.android.wm.shell.bubbles.animation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.Mockito.when;
 
 import android.annotation.SuppressLint;
 import android.content.res.Configuration;
@@ -37,6 +39,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -56,18 +59,22 @@
     private int mStackOffset;
     private PointF mExpansionPoint;
     private BubblePositioner mPositioner;
+    private BubbleStackView.StackViewState mStackViewState;
 
     @SuppressLint("VisibleForTests")
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
+        BubbleStackView stackView = mock(BubbleStackView.class);
+        when(stackView.getState()).thenReturn(getStackViewState());
         mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class));
         mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
                 Insets.of(0, 0, 0, 0),
                 new Rect(0, 0, mDisplayWidth, mDisplayHeight));
         mExpandedController = new ExpandedAnimationController(mPositioner,
-                mOnBubbleAnimatedOutAction);
+                mOnBubbleAnimatedOutAction,
+                stackView);
         spyOn(mExpandedController);
 
         addOneMoreThanBubbleLimitBubbles();
@@ -78,6 +85,13 @@
         mExpansionPoint = new PointF(100, 100);
     }
 
+    public BubbleStackView.StackViewState getStackViewState() {
+        mStackViewState.numberOfBubbles = mLayout.getChildCount();
+        mStackViewState.selectedIndex = 0;
+        mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
+        return mStackViewState;
+    }
+
     @Test
     @Ignore
     public void testExpansionAndCollapse() throws InterruptedException {
@@ -141,12 +155,10 @@
 
     /** Check that children are in the correct positions for being expanded. */
     private void testBubblesInCorrectExpandedPositions() {
-        boolean onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
         // Check all the visible bubbles to see if they're in the right place.
         for (int i = 0; i < mLayout.getChildCount(); i++) {
             PointF expectedPosition = mPositioner.getExpandedBubbleXY(i,
-                    mLayout.getChildCount(),
-                    onLeft);
+                    getStackViewState());
             assertEquals(expectedPosition.x,
                     mLayout.getChildAt(i).getTranslationX(),
                     2f);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5db9ddf..38f9607 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2443,17 +2443,11 @@
 
     /**
      * Return a handle to the optional platform's {@link Spatializer}
-     * @return {@code null} if spatialization is not supported, the {@code Spatializer} instance
-     *         otherwise.
+     * @return the {@code Spatializer} instance.
+     * @see Spatializer#getImmersiveAudioLevel() to check for the level of support of the effect
+     *   on the platform
      */
-    public @Nullable Spatializer getSpatializer() {
-        int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
-        try {
-            level = getService().getSpatializerImmersiveAudioLevel();
-        } catch (Exception e) { /* using NONE */ }
-        if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
-            return null;
-        }
+    public @NonNull Spatializer getSpatializer() {
         return new Spatializer(this);
     }
 
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 3ed8b58..d8519b6 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -114,6 +114,7 @@
 
     /** @hide */
     @IntDef(flag = false, value = {
+            SPATIALIZER_IMMERSIVE_LEVEL_OTHER,
             SPATIALIZER_IMMERSIVE_LEVEL_NONE,
             SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
     })
@@ -121,21 +122,42 @@
     public @interface ImmersiveAudioLevel {};
 
     /**
-     * @hide
+     * Constant indicating the {@code Spatializer} on this device supports a spatialization
+     * mode that differs from the ones available at this SDK level.
+     * @see #getImmersiveAudioLevel()
+     */
+    public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1;
+
+    /**
      * Constant indicating there are no spatialization capabilities supported on this device.
-     * @see AudioManager#getImmersiveAudioLevel()
+     * @see #getImmersiveAudioLevel()
      */
     public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
 
     /**
-     * @hide
-     * Constant indicating the {@link Spatializer} on this device supports multichannel
+     * Constant indicating the {@code Spatializer} on this device supports multichannel
      * spatialization.
-     * @see AudioManager#getImmersiveAudioLevel()
+     * @see #getImmersiveAudioLevel()
      */
     public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
 
     /**
+     * Return the level of support for the spatialization feature on this device.
+     * This level of support is independent of whether the {@code Spatializer} is currently
+     * enabled or available and will not change over time.
+     * @return the level of spatialization support
+     * @see #isEnabled()
+     * @see #isAvailable()
+     */
+    public @ImmersiveAudioLevel int getImmersiveAudioLevel() {
+        int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+        try {
+            level = mAm.getService().getSpatializerImmersiveAudioLevel();
+        } catch (Exception e) { /* using NONE */ }
+        return level;
+    }
+
+    /**
      * @hide
      * Enables / disables the spatializer effect.
      * Changing the enabled state will trigger the public
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0d18b8d..e509777 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -22,6 +22,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+        },
+        {
+            "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
         }
       ]
     },
@@ -82,6 +85,9 @@
         },
         {
             "exclude-annotation": "android.platform.helpers.Staging"
+        },
+        {
+            "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
         }
       ]
     }
@@ -101,6 +107,9 @@
         },
         {
             "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+            "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
         }
       ]
     }
@@ -117,5 +126,24 @@
         }
       ]
     }
+  ],
+  "large-screen-postsubmit": [
+      {
+        "name": "PlatformScenarioTests",
+        "options" : [
+          {
+              "include-filter": "android.platform.test.scenario.sysui"
+          },
+          {
+              "include-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
+          },
+          {
+              "exclude-annotation": "org.junit.Ignore"
+          },
+          {
+              "exclude-annotation": "androidx.test.filters.FlakyTest"
+          }
+        ]
+      }
   ]
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index e5933e6..9010d51 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -36,6 +36,13 @@
      * [View.setTranslationY]
      */
     private val translationApplier: TranslationApplier = object : TranslationApplier {},
+    /**
+     * Allows to set custom implementation for getting
+     * view location. Could be useful if logical view bounds
+     * are different than actual bounds (e.g. view container may
+     * have larger width than width of the items in the container)
+     */
+    private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}
 ) : UnfoldTransitionProgressProvider.TransitionProgressListener {
 
     private val screenSize = Point()
@@ -43,6 +50,8 @@
 
     private val animatedViews: MutableList<AnimatedView> = arrayListOf()
 
+    private var lastAnimationProgress: Float = 0f
+
     /**
      * Updates display properties in order to calculate the initial position for the views
      * Must be called before [registerViewForAnimation]
@@ -58,6 +67,19 @@
     }
 
     /**
+     * If target view positions have changed (e.g. because of layout changes) call this method
+     * to re-query view positions and update the translations
+     */
+    fun updateViewPositions() {
+        animatedViews.forEach { animatedView ->
+            animatedView.view.get()?.let {
+                animatedView.updateAnimatedView(it)
+            }
+        }
+        onTransitionProgress(lastAnimationProgress)
+    }
+
+    /**
      * Registers a view to be animated, the view should be measured and layouted
      * After finishing the animation it is necessary to clear
      * the views using [clearRegisteredViews]
@@ -85,45 +107,30 @@
                 )
             }
         }
+        lastAnimationProgress = progress
     }
 
-    private fun createAnimatedView(view: View): AnimatedView {
-        val viewCenter = getViewCenter(view)
+    private fun createAnimatedView(view: View): AnimatedView =
+        AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
+
+    private fun AnimatedView.updateAnimatedView(view: View): AnimatedView {
+        val viewCenter = Point()
+        viewCenterProvider.getViewCenter(view, viewCenter)
+
         val viewCenterX = viewCenter.x
         val viewCenterY = viewCenter.y
 
-        val translationX: Float
-        val translationY: Float
-
         if (isVerticalFold) {
             val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
-            translationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
-            translationY = 0f
+            startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
+            startTranslationY = 0f
         } else {
             val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
-            translationX = 0f
-            translationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
+            startTranslationX = 0f
+            startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
         }
 
-        return AnimatedView(
-            view = WeakReference(view),
-            startTranslationX = translationX,
-            startTranslationY = translationY
-        )
-    }
-
-    private fun getViewCenter(view: View): Point {
-        val viewLocation = IntArray(2)
-        view.getLocationOnScreen(viewLocation)
-
-        val viewX = viewLocation[0]
-        val viewY = viewLocation[1]
-
-        val outPoint = Point()
-        outPoint.x = viewX + view.width / 2
-        outPoint.y = viewY + view.height / 2
-
-        return outPoint
+        return this
     }
 
     /**
@@ -139,10 +146,29 @@
         }
     }
 
+    /**
+     * Interface that allows to use custom logic to get the center of the view
+     */
+    interface ViewCenterProvider {
+        /**
+         * Called when we need to get the center of the view
+         */
+        fun getViewCenter(view: View, outPoint: Point) {
+            val viewLocation = IntArray(2)
+            view.getLocationOnScreen(viewLocation)
+
+            val viewX = viewLocation[0]
+            val viewY = viewLocation[1]
+
+            outPoint.x = viewX + view.width / 2
+            outPoint.y = viewY + view.height / 2
+        }
+    }
+
     private class AnimatedView(
         val view: WeakReference<View>,
-        val startTranslationX: Float,
-        val startTranslationY: Float
+        var startTranslationX: Float = 0f,
+        var startTranslationY: Float = 0f
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 3167070..ecc3245 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -30,6 +30,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.WindowVisibleState;
@@ -46,6 +47,7 @@
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
 
 import javax.inject.Inject;
@@ -126,6 +128,8 @@
                 .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
                 .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
                         allowSystemGestureIgnoringBarVisibility())
+                .setFlag(SYSUI_STATE_SCREEN_PINNING,
+                        ActivityManagerWrapper.getInstance().isScreenPinningActive())
                 .commitUpdate(mDisplayId);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9ceeb75..4e9b0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -96,6 +96,7 @@
     private final UiEventLogger mUiEventLogger;
     private final InstanceIdSequence mInstanceIdSequence;
     private final CustomTileStatePersister mCustomTileStatePersister;
+    private final FeatureFlags mFeatureFlags;
 
     private final List<Callback> mCallbacks = new ArrayList<>();
     private AutoTileManager mAutoTiles;
@@ -123,7 +124,8 @@
             UiEventLogger uiEventLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
-            CustomTileStatePersister customTileStatePersister
+            CustomTileStatePersister customTileStatePersister,
+            FeatureFlags featureFlags
     ) {
         mIconController = iconController;
         mContext = context;
@@ -145,6 +147,7 @@
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
         mCustomTileStatePersister = customTileStatePersister;
+        mFeatureFlags = featureFlags;
 
         mainHandler.post(() -> {
             // This is technically a hack to avoid circular dependency of
@@ -266,7 +269,7 @@
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
-        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
+        final List<String> tileSpecs = loadTileSpecs(mContext, newValue, mFeatureFlags);
         int currentUser = mUserTracker.getUserId();
         if (currentUser != mCurrentUser) {
             mUserContext = mUserTracker.getUserContext();
@@ -335,7 +338,7 @@
         if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
             // If we didn't manage to create any tiles, set it to empty (default)
             Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
-            changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
+            changeTiles(currentSpecs, loadTileSpecs(mContext, "", mFeatureFlags));
         } else {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 mCallbacks.get(i).onTilesChanged();
@@ -403,7 +406,7 @@
 
     private void changeTileSpecs(Predicate<List<String>> changeFunction) {
         final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
-        final List<String> tileSpecs = loadTileSpecs(mContext, setting);
+        final List<String> tileSpecs = loadTileSpecs(mContext, setting, mFeatureFlags);
         if (changeFunction.test(tileSpecs)) {
             saveTilesToSettings(tileSpecs);
         }
@@ -492,7 +495,8 @@
         throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
     }
 
-    protected static List<String> loadTileSpecs(Context context, String tileList) {
+    protected static List<String> loadTileSpecs(
+            Context context, String tileList, FeatureFlags featureFlags) {
         final Resources res = context.getResources();
 
         if (TextUtils.isEmpty(tileList)) {
@@ -525,6 +529,21 @@
                 }
             }
         }
+        if (featureFlags.isProviderModelSettingEnabled()) {
+            if (!tiles.contains("internet")) {
+                if (tiles.contains("wifi")) {
+                    // Replace the WiFi with Internet, and remove the Cell
+                    tiles.set(tiles.indexOf("wifi"), "internet");
+                    tiles.remove("cell");
+                } else if (tiles.contains("cell")) {
+                    // Replace the Cell with Internet
+                    tiles.set(tiles.indexOf("cell"), "internet");
+                }
+            } else {
+                tiles.remove("wifi");
+                tiles.remove("cell");
+            }
+        }
         return tiles;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 1c20a86..993bbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -63,6 +63,7 @@
     private final Executor mBgExecutor;
     private final Context mContext;
     private final UserTracker mUserTracker;
+    private final FeatureFlags mFeatureFlags;
     private TileStateListener mListener;
 
     private boolean mFinished;
@@ -72,12 +73,14 @@
             Context context,
             UserTracker userTracker,
             @Main Executor mainExecutor,
-            @Background Executor bgExecutor
+            @Background Executor bgExecutor,
+            FeatureFlags featureFlags
     ) {
         mContext = context;
         mMainExecutor = mainExecutor;
         mBgExecutor = bgExecutor;
         mUserTracker = userTracker;
+        mFeatureFlags = featureFlags;
     }
 
     public void setListener(TileStateListener listener) {
@@ -118,6 +121,10 @@
         }
 
         final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
+        if (mFeatureFlags.isProviderModelSettingEnabled()) {
+            possibleTiles.remove("cell");
+            possibleTiles.remove("wifi");
+        }
 
         for (String spec : possibleTiles) {
             // Only add current and stock tiles that can be created from QSFactoryImpl.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
new file mode 100644
index 0000000..cf34db2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.statusbar
+
+import android.app.StatusBarManager.DISABLE_BACK
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_EXPAND
+import android.app.StatusBarManager.DISABLE_HOME
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_RECENT
+import android.app.StatusBarManager.DISABLE_SEARCH
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A singleton that creates concise but readable strings representing the values of the disable
+ * flags for debugging.
+ *
+ * See [CommandQueue.disable] for information about disable flags.
+ *
+ * Note that, for both lists passed in, each flag must have a distinct [DisableFlag.flagIsSetSymbol]
+ * and distinct [DisableFlag.flagNotSetSymbol] within the list. If this isn't true, the logs could
+ * be ambiguous so an [IllegalArgumentException] is thrown.
+ */
+@SysUISingleton
+class DisableFlagsLogger constructor(
+    private val disable1FlagsList: List<DisableFlag>,
+    private val disable2FlagsList: List<DisableFlag>
+) {
+
+    @Inject
+    constructor() : this(defaultDisable1FlagsList, defaultDisable2FlagsList)
+
+    init {
+        if (flagsListHasDuplicateSymbols(disable1FlagsList)) {
+            throw IllegalArgumentException("disable1 flags must have unique symbols")
+        }
+        if (flagsListHasDuplicateSymbols(disable2FlagsList)) {
+            throw IllegalArgumentException("disable2 flags must have unique symbols")
+        }
+    }
+
+    private fun flagsListHasDuplicateSymbols(list: List<DisableFlag>): Boolean {
+        val numDistinctFlagOffStatus = list.map { it.getFlagStatus(0) }.distinct().count()
+        val numDistinctFlagOnStatus = list
+                .map { it.getFlagStatus(Int.MAX_VALUE) }
+                .distinct()
+                .count()
+        return numDistinctFlagOffStatus < list.count() || numDistinctFlagOnStatus < list.count()
+    }
+
+    /**
+     * Returns a string representing the, old, new, and new-after-modification disable flag states,
+     * as well as the differences between each of the states.
+     *
+     * Example:
+     *   Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (hB.iGR) | New after local modification:
+     *   EnaihBcRso.qInGR (.n)
+     *
+     * A capital character signifies the flag is set and a lowercase character signifies that the
+     * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
+     *
+     * The difference between states is written between parentheses, and won't be included if there
+     * is no difference. the new-after-modification state also won't be included if there's no
+     * difference from the new state.
+     *
+     * @param old the disable state that had been previously sent.
+     * @param new the new disable state that has just been sent.
+     * @param newAfterLocalModification the new disable states after a class has locally modified
+     *   them. Null if the class does not locally modify.
+     */
+    fun getDisableFlagsString(
+        old: DisableState,
+        new: DisableState,
+        newAfterLocalModification: DisableState? = null
+    ): String {
+        val builder = StringBuilder("Received new disable state. ")
+        builder.append("Old: ")
+        builder.append(getFlagsString(old))
+        builder.append(" | New: ")
+        if (old != new) {
+            builder.append(getFlagsStringWithDiff(old, new))
+        } else {
+            builder.append(getFlagsString(old))
+        }
+
+        if (newAfterLocalModification != null && new != newAfterLocalModification) {
+            builder.append(" | New after local modification: ")
+            builder.append(getFlagsStringWithDiff(new, newAfterLocalModification))
+        }
+
+        return builder.toString()
+    }
+
+    /**
+     * Returns a string representing [new] state, as well as the difference from [old] to [new]
+     * (if there is one).
+     */
+    private fun getFlagsStringWithDiff(old: DisableState, new: DisableState): String {
+        val builder = StringBuilder()
+        builder.append(getFlagsString(new))
+        builder.append(" ")
+        builder.append(getDiffString(old, new))
+        return builder.toString()
+    }
+
+    /**
+     * Returns a string representing the difference between [old] and [new], or an empty string if
+     * there is no difference.
+     *
+     * For example, if old was "abc.DE" and new was "aBC.De", the difference returned would be
+     * "(BC.e)".
+     */
+    private fun getDiffString(old: DisableState, new: DisableState): String {
+        if (old == new) {
+            return ""
+        }
+
+        val builder = StringBuilder("(")
+        disable1FlagsList.forEach {
+            val newSymbol = it.getFlagStatus(new.disable1)
+            if (it.getFlagStatus(old.disable1) != newSymbol) {
+                builder.append(newSymbol)
+            }
+        }
+        builder.append(".")
+        disable2FlagsList.forEach {
+            val newSymbol = it.getFlagStatus(new.disable2)
+            if (it.getFlagStatus(old.disable2) != newSymbol) {
+                builder.append(newSymbol)
+            }
+        }
+        builder.append(")")
+        return builder.toString()
+    }
+
+    /** Returns a string representing the disable flag states, e.g. "EnaihBcRso.qiNGR".  */
+    private fun getFlagsString(state: DisableState): String {
+        val builder = StringBuilder("")
+        disable1FlagsList.forEach { builder.append(it.getFlagStatus(state.disable1)) }
+        builder.append(".")
+        disable2FlagsList.forEach { builder.append(it.getFlagStatus(state.disable2)) }
+        return builder.toString()
+    }
+
+    /** A POJO representing each disable flag. */
+    class DisableFlag(
+        private val bitMask: Int,
+        private val flagIsSetSymbol: Char,
+        private val flagNotSetSymbol: Char
+    ) {
+
+        /**
+         * Returns a character representing whether or not this flag is set in [state].
+         *
+         * A capital character signifies the flag is set and a lowercase character signifies that
+         * the flag isn't set.
+         */
+        internal fun getFlagStatus(state: Int): Char =
+            if (0 != state and this.bitMask) this.flagIsSetSymbol
+            else this.flagNotSetSymbol
+    }
+
+    /** POJO to hold [disable1] and [disable2]. */
+    data class DisableState(val disable1: Int, val disable2: Int)
+}
+
+// LINT.IfChange
+private val defaultDisable1FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
+        DisableFlagsLogger.DisableFlag(DISABLE_EXPAND, 'E', 'e'),
+        DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ICONS, 'N', 'n'),
+        DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ALERTS, 'A', 'a'),
+        DisableFlagsLogger.DisableFlag(DISABLE_SYSTEM_INFO, 'I', 'i'),
+        DisableFlagsLogger.DisableFlag(DISABLE_HOME, 'H', 'h'),
+        DisableFlagsLogger.DisableFlag(DISABLE_BACK, 'B', 'b'),
+        DisableFlagsLogger.DisableFlag(DISABLE_CLOCK, 'C', 'c'),
+        DisableFlagsLogger.DisableFlag(DISABLE_RECENT, 'R', 'r'),
+        DisableFlagsLogger.DisableFlag(DISABLE_SEARCH, 'S', 's'),
+        DisableFlagsLogger.DisableFlag(DISABLE_ONGOING_CALL_CHIP, 'O', 'o')
+)
+
+private val defaultDisable2FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
+        DisableFlagsLogger.DisableFlag(DISABLE2_QUICK_SETTINGS, 'Q', 'q'),
+        DisableFlagsLogger.DisableFlag(DISABLE2_SYSTEM_ICONS, 'I', 'i'),
+        DisableFlagsLogger.DisableFlag(DISABLE2_NOTIFICATION_SHADE, 'N', 'n'),
+        DisableFlagsLogger.DisableFlag(DISABLE2_GLOBAL_ACTIONS, 'G', 'g'),
+        DisableFlagsLogger.DisableFlag(DISABLE2_ROTATE_SUGGESTIONS, 'R', 'r')
+)
+// LINT.ThenChange(frameworks/base/core/java/android/app/StatusBarManager.java)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 51eb496..abee7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -225,19 +225,19 @@
             }
         }
 
+        // If no one is light, all icons become white.
+        if (numLightStacks == 0) {
+            mStatusBarIconController.getTransitionsController().setIconsDark(
+                    false, animateChange());
+        }
+
         // If all stacks are light, all icons get dark.
-        if (numLightStacks == numStacks) {
+        else if (numLightStacks == numStacks) {
             mStatusBarIconController.setIconsDarkArea(null);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
 
         }
 
-        // If no one is light, all icons become white.
-        else if (numLightStacks == 0) {
-            mStatusBarIconController.getTransitionsController().setIconsDark(
-                    false, animateChange());
-        }
-
         // Not the same for every stack, magic!
         else {
             mStatusBarIconController.setIconsDarkArea(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
deleted file mode 100644
index b36b67d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
+++ /dev/null
@@ -1,39 +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.statusbar.phone;
-
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.ViewController;
-
-/** Controller for {@link PhoneStatusBarView}. */
-public class PhoneStatusBarViewController extends ViewController<PhoneStatusBarView> {
-
-    protected PhoneStatusBarViewController(
-            PhoneStatusBarView view,
-            CommandQueue commandQueue) {
-        super(view);
-        mView.setPanelEnabledProvider(commandQueue::panelsEnabled);
-    }
-
-    @Override
-    protected void onViewAttached() {
-    }
-
-    @Override
-    protected void onViewDetached() {
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
new file mode 100644
index 0000000..9799533
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.statusbar.phone
+
+import android.graphics.Point
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.ViewController
+
+/** Controller for [PhoneStatusBarView].  */
+class PhoneStatusBarViewController(
+    view: PhoneStatusBarView,
+    commandQueue: CommandQueue,
+    statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?
+) : ViewController<PhoneStatusBarView>(view) {
+
+    override fun onViewAttached() {}
+    override fun onViewDetached() {}
+
+    init {
+        mView.setPanelEnabledProvider {
+            commandQueue.panelsEnabled()
+        }
+
+        statusBarMoveFromCenterAnimationController?.let { animationController ->
+            val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
+            val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+
+            val viewCenterProvider = StatusBarViewsCenterProvider()
+            val viewsToAnimate = arrayOf(
+                statusBarLeftSide,
+                systemIconArea
+            )
+
+            animationController.init(viewsToAnimate, viewCenterProvider)
+
+            mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
+                val widthChanged = right - left != oldRight - oldLeft
+                if (widthChanged) {
+                    statusBarMoveFromCenterAnimationController.onStatusBarWidthChanged()
+                }
+            }
+        }
+    }
+
+    private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
+        override fun getViewCenter(view: View, outPoint: Point) =
+            when (view.id) {
+                R.id.status_bar_left_side -> {
+                    // items aligned to the start, return start center point
+                    getViewEdgeCenter(view, outPoint, isStart = true)
+                }
+                R.id.system_icon_area -> {
+                    // items aligned to the end, return end center point
+                    getViewEdgeCenter(view, outPoint, isStart = false)
+                }
+                else -> super.getViewCenter(view, outPoint)
+            }
+
+        /**
+         * Returns start or end (based on [isStart]) center point of the view
+         */
+        private fun getViewEdgeCenter(view: View, outPoint: Point, isStart: Boolean) {
+            val isRtl = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+            val isLeftEdge = isRtl xor isStart
+
+            val viewLocation = IntArray(2)
+            view.getLocationOnScreen(viewLocation)
+
+            val viewX = viewLocation[0]
+            val viewY = viewLocation[1]
+
+            outPoint.x = viewX + if (isLeftEdge) view.height / 2 else view.width - view.height / 2
+            outPoint.y = viewY + view.height / 2
+        }
+    }
+}
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 685b062..32c4a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -536,6 +536,7 @@
     private final FeatureFlags mFeatureFlags;
     private final UnfoldTransitionConfig mUnfoldTransitionConfig;
     private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
+    private final Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimation;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final MessageRouter mMessageRouter;
     private final WallpaperManager mWallpaperManager;
@@ -768,6 +769,7 @@
             BrightnessSlider.Factory brightnessSliderFactory,
             UnfoldTransitionConfig unfoldTransitionConfig,
             Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+            Lazy<StatusBarMoveFromCenterAnimationController> statusBarUnfoldAnimationController,
             OngoingCallController ongoingCallController,
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
@@ -860,6 +862,7 @@
         mBrightnessSliderFactory = brightnessSliderFactory;
         mUnfoldTransitionConfig = unfoldTransitionConfig;
         mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation;
+        mMoveFromCenterAnimation = statusBarUnfoldAnimationController;
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
         mStatusBarLocationPublisher = locationPublisher;
@@ -1141,8 +1144,13 @@
                         sendInitialExpansionAmount(listener);
                     }
 
+                    StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null;
+                    if (mUnfoldTransitionConfig.isEnabled()) {
+                        moveFromCenterAnimation = mMoveFromCenterAnimation.get();
+                    }
                     mPhoneStatusBarViewController =
-                            new PhoneStatusBarViewController(mStatusBarView, mCommandQueue);
+                            new PhoneStatusBarViewController(mStatusBarView, mCommandQueue,
+                                    moveFromCenterAnimation);
                     mPhoneStatusBarViewController.init();
 
                     mBatteryMeterViewController = new BatteryMeterViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index 6a510c9..5301b25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -57,6 +57,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -98,6 +99,7 @@
     private final VibratorHelper mVibratorHelper;
     private final Optional<Vibrator> mVibratorOptional;
     private final LightBarController mLightBarController;
+    private final DisableFlagsLogger mDisableFlagsLogger;
     private final int mDisplayId;
     private final boolean mVibrateOnOpening;
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
@@ -134,6 +136,7 @@
             VibratorHelper vibratorHelper,
             Optional<Vibrator> vibratorOptional,
             LightBarController lightBarController,
+            DisableFlagsLogger disableFlagsLogger,
             @DisplayId int displayId) {
 
         mStatusBar = statusBar;
@@ -159,6 +162,7 @@
         mVibratorHelper = vibratorHelper;
         mVibratorOptional = vibratorOptional;
         mLightBarController = lightBarController;
+        mDisableFlagsLogger = disableFlagsLogger;
         mDisplayId = displayId;
 
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
@@ -267,7 +271,17 @@
         if (displayId != mDisplayId) {
             return;
         }
+
+        int state2BeforeAdjustment = state2;
         state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+        Log.d(StatusBar.TAG,
+                mDisableFlagsLogger.getDisableFlagsString(
+                        /* old= */ new DisableFlagsLogger.DisableState(
+                                mStatusBar.getDisabled1(), mStatusBar.getDisabled2()),
+                        /* new= */ new DisableFlagsLogger.DisableState(
+                                state1, state2BeforeAdjustment),
+                        /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState(
+                                state1, state2)));
 
         final int old1 = mStatusBar.getDisabled1();
         final int diff1 = state1 ^ old1;
@@ -277,43 +291,6 @@
         final int diff2 = state2 ^ old2;
         mStatusBar.setDisabled2(state2);
 
-        if (StatusBar.DEBUG) {
-            Log.d(StatusBar.TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)",
-                    old1, state1, diff1));
-            Log.d(StatusBar.TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)",
-                    old2, state2, diff2));
-        }
-
-        StringBuilder flagdbg = new StringBuilder();
-        flagdbg.append("disable<");
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_EXPAND))               ? 'E' : 'e');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_EXPAND))               ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS))   ? 'I' : 'i');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_NOTIFICATION_ICONS))   ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS))  ? 'A' : 'a');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_NOTIFICATION_ALERTS))  ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO))          ? 'S' : 's');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_SYSTEM_INFO))          ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_BACK))                 ? 'B' : 'b');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_BACK))                 ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_HOME))                 ? 'H' : 'h');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_HOME))                 ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_RECENT))               ? 'R' : 'r');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_RECENT))               ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_CLOCK))                ? 'C' : 'c');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_CLOCK))                ? '!' : ' ');
-        flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH))               ? 'S' : 's');
-        flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_SEARCH))               ? '!' : ' ');
-        flagdbg.append("> disable2<");
-        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS))      ? 'Q' : 'q');
-        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_QUICK_SETTINGS))      ? '!' : ' ');
-        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS))        ? 'I' : 'i');
-        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_SYSTEM_ICONS))        ? '!' : ' ');
-        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))  ? 'N' : 'n');
-        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))  ? '!' : ' ');
-        flagdbg.append('>');
-        Log.d(StatusBar.TAG, flagdbg.toString());
-
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
                 mShadeController.animateCollapsePanels();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
new file mode 100644
index 0000000..8af03aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.statusbar.phone
+
+import android.view.View
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+
+@SysUISingleton
+class StatusBarMoveFromCenterAnimationController @Inject constructor(
+    private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    private val windowManager: WindowManager
+) {
+
+    private lateinit var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator
+
+    fun init(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
+        moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
+            viewCenterProvider = viewCenterProvider)
+
+        unfoldTransitionProgressProvider.addCallback(object : TransitionProgressListener {
+            override fun onTransitionStarted() {
+                moveFromCenterAnimator.updateDisplayProperties()
+
+                viewsToAnimate.forEach {
+                    moveFromCenterAnimator.registerViewForAnimation(it)
+                }
+            }
+
+            override fun onTransitionFinished() {
+                moveFromCenterAnimator.onTransitionFinished()
+                moveFromCenterAnimator.clearRegisteredViews()
+            }
+
+            override fun onTransitionProgress(progress: Float) {
+                moveFromCenterAnimator.onTransitionProgress(progress)
+            }
+        })
+    }
+
+    fun onStatusBarWidthChanged() {
+        moveFromCenterAnimator.updateViewPositions()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 63ee701..befea41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -90,6 +90,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
+import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -213,6 +214,7 @@
             BrightnessSlider.Factory brightnessSliderFactory,
             UnfoldTransitionConfig unfoldTransitionConfig,
             Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+            Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation,
             OngoingCallController ongoingCallController,
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
@@ -307,6 +309,7 @@
                 brightnessSliderFactory,
                 unfoldTransitionConfig,
                 unfoldLightRevealOverlayAnimation,
+                statusBarMoveFromCenterAnimation,
                 ongoingCallController,
                 animationScheduler,
                 locationPublisher,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb2..8d615f0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -226,7 +226,7 @@
     }
 
     @Test
-    public void testDetachRemovesSmartspaceView() {
+    public void testDetachDisconnectsSmartspace() {
         when(mSmartspaceController.isEnabled()).thenReturn(true);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mController.init();
@@ -237,7 +237,7 @@
         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
         listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
-        verify(mView).removeView(mFakeSmartspaceView);
+        verify(mSmartspaceController).disconnect();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 109721f..12c0d53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -95,6 +96,8 @@
     private KeyguardBypassController mBypassController;
     @Mock
     private FalsingManager mFalsingManager;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     public QSFragmentTest() {
         super(QSFragment.class);
@@ -136,7 +139,7 @@
                 () -> mock(AutoTileManager.class), mock(DumpManager.class),
                 mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
                 mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
-                mock(SecureSettings.class), mock(CustomTileStatePersister.class));
+                mock(SecureSettings.class), mock(CustomTileStatePersister.class), mFeatureFlags);
         qs.setHost(host);
 
         qs.setListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 6c7f770..6bb2b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -121,6 +121,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private CustomTileStatePersister mCustomTileStatePersister;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     private Handler mHandler;
     private TestableLooper mLooper;
@@ -138,8 +140,9 @@
         mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
                 mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
                 mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
-                mSecureSettings, mCustomTileStatePersister);
+                mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
         setUpTileFactory();
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
     }
 
     private void setUpTileFactory() {
@@ -167,13 +170,13 @@
 
     @Test
     public void testLoadTileSpecs_emptySetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, "");
+        List<String> tiles = QSTileHost.loadTileSpecs(mContext, "", mFeatureFlags);
         assertFalse(tiles.isEmpty());
     }
 
     @Test
     public void testLoadTileSpecs_nullSetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, null);
+        List<String> tiles = QSTileHost.loadTileSpecs(mContext, null, mFeatureFlags);
         assertFalse(tiles.isEmpty());
     }
 
@@ -187,6 +190,55 @@
     }
 
     @Test
+    public void testRemoveWifiAndCellularWithoutInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2");
+
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testRemoveWifiAndCellularWithInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testRemoveWifiWithoutInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testRemoveCellWithInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testNoWifiNoCellularNoInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+    }
+
+    @Test
     public void testSpecWithInvalidDoesNotUseDefault() {
         mContext.getOrCreateTestableResources()
                 .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
@@ -319,7 +371,7 @@
 
     @Test
     public void testLoadTileSpec_repeated() {
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
+        List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2", mFeatureFlags);
 
         assertEquals(2, specs.size());
         assertEquals("spec1", specs.get(0));
@@ -330,7 +382,7 @@
     public void testLoadTileSpec_repeatedInDefault() {
         mContext.getOrCreateTestableResources()
                 .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
+        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default", mFeatureFlags);
 
         // Remove spurious tiles, like dbg:mem
         specs.removeIf(spec -> !"spec1".equals(spec));
@@ -341,7 +393,7 @@
     public void testLoadTileSpec_repeatedDefaultAndSetting() {
         mContext.getOrCreateTestableResources()
                 .addOverride(R.string.quick_settings_tiles_default, "spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
+        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1", mFeatureFlags);
 
         // Remove spurious tiles, like dbg:mem
         specs.removeIf(spec -> !"spec1".equals(spec));
@@ -379,11 +431,12 @@
                 Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
                 BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
                 UiEventLogger uiEventLogger, UserTracker userTracker,
-                SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) {
+                SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
+                FeatureFlags featureFlags) {
             super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
                     tunerService, autoTiles, dumpManager, broadcastDispatcher,
                     Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings,
-                    customTileStatePersister);
+                    customTileStatePersister, featureFlags);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 8546a35..018806e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -109,6 +109,8 @@
     private PackageManager mPackageManager;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
 
@@ -134,12 +136,12 @@
                     }
                 }
         ).when(mQSTileHost).createTile(anyString());
-
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
         FakeSystemClock clock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(clock);
         mBgExecutor = new FakeExecutor(clock);
         mTileQueryHelper = new TileQueryHelper(
-                mContext, mUserTracker, mMainExecutor, mBgExecutor);
+                mContext, mUserTracker, mMainExecutor, mBgExecutor, mFeatureFlags);
         mTileQueryHelper.setListener(mListener);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 2b18404..e3045eb2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -98,6 +99,8 @@
     private UserTracker mUserTracker;
     @Mock
     private SecureSettings  mSecureSettings;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     @Before
     public void setUp() throws Exception {
@@ -119,7 +122,8 @@
                 mUiEventLogger,
                 mUserTracker,
                 mSecureSettings,
-                mock(CustomTileStatePersister.class));
+                mock(CustomTileStatePersister.class),
+                mFeatureFlags);
         mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
                 mUserTracker);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index ebc6f2a..6a68b71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -157,6 +157,21 @@
         assertThat(view.translationY).isWithin(0.01f).of(3.75f)
     }
 
+    @Test
+    fun testUpdateViewPositions_viewOnTheLeftAndMovedToTheRight_viewTranslatedToTheLeft() {
+        givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+        val view = createView(x = 20)
+        animator.registerViewForAnimation(view)
+        animator.onTransitionStarted()
+        animator.onTransitionProgress(0.5f)
+        view.updateMock(x = 80) // view moved from the left side to the right
+
+        animator.updateViewPositions()
+
+        // Negative translationX -> translated to the left
+        assertThat(view.translationX).isWithin(0.1f).of(-5.25f)
+    }
+
     private fun createView(
         x: Int = 0,
         y: Int = 0,
@@ -176,7 +191,30 @@
         whenever(view.width).thenReturn(width)
         whenever(view.height).thenReturn(height)
 
-        return view.apply {
+        view.updateMock(x, y, width, height, translationX, translationY)
+
+        return view
+    }
+
+    private fun View.updateMock(
+        x: Int = 0,
+        y: Int = 0,
+        width: Int = 10,
+        height: Int = 10,
+        translationX: Float = 0f,
+        translationY: Float = 0f
+    ) {
+        doAnswer {
+            val location = (it.arguments[0] as IntArray)
+            location[0] = x
+            location[1] = y
+            Unit
+        }.`when`(this).getLocationOnScreen(any())
+
+        whenever(this.width).thenReturn(width)
+        whenever(this.height).thenReturn(height)
+
+        this.apply {
             setTranslationX(translationX)
             setTranslationY(translationY)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
new file mode 100644
index 0000000..096efad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.statusbar
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+
+@SmallTest
+class DisableFlagsLoggerTest : SysuiTestCase() {
+    private val disable1Flags = listOf(
+            DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+            DisableFlagsLogger.DisableFlag(0b010, 'B', 'b'),
+            DisableFlagsLogger.DisableFlag(0b001, 'C', 'c'),
+    )
+    private val disable2Flags = listOf(
+            DisableFlagsLogger.DisableFlag(0b10, 'M', 'm'),
+            DisableFlagsLogger.DisableFlag(0b01, 'N', 'n'),
+    )
+
+    private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags)
+
+    @Test
+    fun getDisableFlagsString_oldAndNewSame_statesLoggedButDiffsNotLogged() {
+        val state = DisableFlagsLogger.DisableState(
+                0b111, // ABC
+                0b01 // mN
+        )
+
+        val result = disableFlagsLogger.getDisableFlagsString(state, state)
+
+        assertThat(result).contains("Old: ABC.mN")
+        assertThat(result).contains("New: ABC.mN")
+        assertThat(result).doesNotContain("(")
+        assertThat(result).doesNotContain(")")
+    }
+
+    @Test
+    fun getDisableFlagsString_oldAndNewDifferent_statesAndDiffLogged() {
+        val result = disableFlagsLogger.getDisableFlagsString(
+                DisableFlagsLogger.DisableState(
+                        0b111, // ABC
+                        0b01, // mN
+                ),
+                DisableFlagsLogger.DisableState(
+                        0b001, // abC
+                        0b10 // Mn
+                )
+        )
+
+        assertThat(result).contains("Old: ABC.mN")
+        assertThat(result).contains("New: abC.Mn")
+        assertThat(result).contains("(ab.Mn)")
+    }
+
+    @Test
+    fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
+        val result = disableFlagsLogger.getDisableFlagsString(
+                DisableFlagsLogger.DisableState(
+                        0b001, // abC
+                        0b01, // mN
+                ),
+                DisableFlagsLogger.DisableState(
+                        0b001, // abC
+                        0b00 // mn
+                )
+        )
+
+        assertThat(result).contains("(.n)")
+    }
+
+    @Test
+    fun getDisableFlagsString_nullLocalModification_localModNotLogged() {
+        val result = disableFlagsLogger.getDisableFlagsString(
+                DisableFlagsLogger.DisableState(0, 0),
+                DisableFlagsLogger.DisableState(1, 1),
+                newAfterLocalModification = null
+        )
+
+        assertThat(result).doesNotContain("local modification")
+    }
+
+    @Test
+    fun getDisableFlagsString_newAfterLocalModificationSameAsNew_localModNotLogged() {
+        val newState =  DisableFlagsLogger.DisableState(
+                0b001, // abC
+                0b10 // mn
+        )
+
+        val result = disableFlagsLogger.getDisableFlagsString(
+                DisableFlagsLogger.DisableState(0, 0), newState, newState
+        )
+
+        assertThat(result).doesNotContain("local modification")
+    }
+
+    @Test
+    fun getDisableFlagsString_newAfterLocalModificationDifferent_localModAndDiffLogged() {
+        val result = disableFlagsLogger.getDisableFlagsString(
+                old = DisableFlagsLogger.DisableState(0, 0),
+                new = DisableFlagsLogger.DisableState(
+                        0b000, // abc
+                        0b00 // mn
+                ),
+                newAfterLocalModification = DisableFlagsLogger.DisableState(
+                        0b100, // Abc
+                        0b10 // Mn
+                )
+        )
+
+        assertThat(result).contains("local modification: Abc.Mn (A.M)")
+    }
+
+    @Test
+    fun constructor_defaultDisableFlags_noException() {
+        // Just creating the logger with the default params will trigger the exception if there
+        // is one.
+        DisableFlagsLogger()
+    }
+
+    @Test
+    fun constructor_disable1_FlagIsSetSymbolNotUnique_exception() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DisableFlagsLogger(
+                    disable1FlagsList = listOf(
+                            DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+                            DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'),
+                    ),
+                    listOf()
+            )
+        }
+    }
+
+    @Test
+    fun constructor_disable1_FlagNotSetSymbolNotUnique_exception() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DisableFlagsLogger(
+                    disable1FlagsList = listOf(
+                            DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+                            DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'),
+                    ),
+                    listOf()
+            )
+        }
+    }
+
+    @Test
+    fun constructor_disable2_FlagIsSetSymbolNotUnique_exception() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DisableFlagsLogger(
+                    disable1FlagsList = listOf(),
+                    disable2FlagsList = listOf(
+                            DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+                            DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'),
+                    ),
+            )
+        }
+    }
+
+    @Test
+    fun constructor_disable2_FlagNotSetSymbolNotUnique_exception() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DisableFlagsLogger(
+                    disable1FlagsList = listOf(),
+                    disable2FlagsList = listOf(
+                            DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+                            DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'),
+                    ),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index d63730d..c7d4794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.view.LayoutInflater
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
 import com.google.common.truth.Truth.assertThat
@@ -24,7 +27,10 @@
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import com.android.systemui.R
+import com.android.systemui.util.mockito.any
 
 @SmallTest
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@@ -32,14 +38,22 @@
     @Mock
     private lateinit var commandQueue: CommandQueue
 
+    @Mock
+    private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
+
     private lateinit var view: PhoneStatusBarView
     private lateinit var controller: PhoneStatusBarViewController
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        view = PhoneStatusBarView(mContext, null)
-        controller = PhoneStatusBarViewController(view, commandQueue)
+        // create the view on main thread as it requires main looper
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            val parent = FrameLayout(mContext) // add parent to keep layout params
+            view = LayoutInflater.from(mContext)
+                .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+        }
+        controller = PhoneStatusBarViewController(view, commandQueue, null)
     }
 
     @Test
@@ -56,4 +70,11 @@
 
         assertThat(providerUsed).isTrue()
     }
+
+    @Test
+    fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() {
+        controller = PhoneStatusBarViewController(view, commandQueue, moveFromCenterAnimation)
+
+        verify(moveFromCenterAnimation).init(any(), any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
index 52538c7..8555306 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -112,6 +113,7 @@
                 mVibratorHelper,
                 Optional.of(mVibrator),
                 mLightBarController,
+                new DisableFlagsLogger(),
                 DEFAULT_DISPLAY);
 
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index b23414b..3c0382b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -254,6 +254,7 @@
     @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
     @Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
     @Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
+    @Mock private Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimationLazy;
     @Mock private OngoingCallController mOngoingCallController;
     @Mock private SystemStatusAnimationScheduler mAnimationScheduler;
     @Mock private StatusBarLocationPublisher mLocationPublisher;
@@ -428,6 +429,7 @@
                 mBrightnessSliderFactory,
                 mUnfoldTransitionConfig,
                 mUnfoldLightRevealOverlayAnimationLazy,
+                mMoveFromCenterAnimationLazy,
                 mOngoingCallController,
                 mAnimationScheduler,
                 mLocationPublisher,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 30de4b4..4748a86 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -566,7 +566,7 @@
                 return PendingIntent.getActivityAsUser(getContext(),
                         0 /* request code */,
                         NotificationAccessConfirmationActivityContract.launcherIntent(
-                                userId, component, packageTitle),
+                                getContext(), userId, component, packageTitle),
                         PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
                                 | PendingIntent.FLAG_CANCEL_CURRENT,
                         null /* options */,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b9b90c0..b230200 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -200,7 +200,8 @@
  */
 public class AudioService extends IAudioService.Stub
         implements AccessibilityManager.TouchExplorationStateChangeListener,
-            AccessibilityManager.AccessibilityServicesStateChangeListener {
+            AccessibilityManager.AccessibilityServicesStateChangeListener,
+            AudioSystemAdapter.OnRoutingUpdatedListener {
 
     private static final String TAG = "AS.AudioService";
 
@@ -314,12 +315,14 @@
     private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38;
     private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
+    private static final int MSG_ROUTING_UPDATED = 41;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
     private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
     private static final int MSG_INIT_STREAMS_VOLUMES = 101;
+    private static final int MSG_INIT_SPATIALIZER = 102;
     // end of messages handled under wakelock
 
     // retry delay in case of failure to indicate system ready to AudioFlinger
@@ -869,6 +872,8 @@
 
         mSfxHelper = new SoundEffectsHelper(mContext);
 
+        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem);
+
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
 
@@ -1033,6 +1038,9 @@
         // done with service initialization, continue additional work in our Handler thread
         queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
                 0 /* arg1 */,  0 /* arg2 */, null /* obj */,  0 /* delay */);
+        queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
+                0 /* arg1 */,  0 /* arg2 */, null /* obj */,  0 /* delay */);
+
     }
 
     /**
@@ -1222,6 +1230,22 @@
         updateVibratorInfos();
     }
 
+    //-----------------------------------------------------------------
+    // routing monitoring from AudioSystemAdapter
+    @Override
+    public void onRoutingUpdatedFromNative() {
+        sendMsg(mAudioHandler,
+                MSG_ROUTING_UPDATED,
+                SENDMSG_REPLACE, 0, 0, null,
+                /*delay*/ 0);
+    }
+
+    void monitorRoutingChanges(boolean enabled) {
+        mAudioSystem.setRoutingListener(enabled ? this : null);
+    }
+
+
+    //-----------------------------------------------------------------
     RoleObserver mRoleObserver;
 
     class RoleObserver implements OnRoleHoldersChangedListener {
@@ -1406,6 +1430,9 @@
             }
         }
 
+        // TODO check property if feature enabled
+        mSpatializerHelper.reset(/* featureEnabled */ SPATIALIZER_FEATURE_ENABLED_DEFAULT);
+
         onIndicateSystemReady();
         // indicate the end of reconfiguration phase to audio HAL
         AudioSystem.setParameters("restarting=false");
@@ -7539,6 +7566,13 @@
                     mAudioEventWakeLock.release();
                     break;
 
+                case MSG_INIT_SPATIALIZER:
+                    mSpatializerHelper.init();
+                    // TODO read property to see if enabled
+                    mSpatializerHelper.setFeatureEnabled(SPATIALIZER_FEATURE_ENABLED_DEFAULT);
+                    mAudioEventWakeLock.release();
+                    break;
+
                 case MSG_CHECK_MUSIC_ACTIVE:
                     onCheckMusicActive((String) msg.obj);
                     break;
@@ -7671,6 +7705,10 @@
                 case MSG_DISPATCH_AUDIO_MODE:
                     dispatchMode(msg.arg1);
                     break;
+
+                case MSG_ROUTING_UPDATED:
+                    mSpatializerHelper.onRoutingUpdated();
+                    break;
             }
         }
     }
@@ -8239,7 +8277,8 @@
     }
 
     //==========================================================================================
-    private final SpatializerHelper mSpatializerHelper = new SpatializerHelper();
+    private final @NonNull SpatializerHelper mSpatializerHelper;
+    private static final boolean SPATIALIZER_FEATURE_ENABLED_DEFAULT = false;
 
     private void enforceModifyDefaultAudioEffectsPermission() {
         if (mContext.checkCallingOrSelfPermission(
@@ -8249,9 +8288,12 @@
         }
     }
 
-    /** @see AudioManager#getSpatializerImmersiveAudioLevel() */
+    /**
+     * Returns the immersive audio level that the platform is capable of
+     * @see Spatializer#getImmersiveAudioLevel()
+     */
     public int getSpatializerImmersiveAudioLevel() {
-        return mSpatializerHelper.getImmersiveAudioLevel();
+        return mSpatializerHelper.getCapableImmersiveAudioLevel();
     }
 
     /** @see Spatializer#isEnabled() */
@@ -8267,7 +8309,7 @@
     /** @see Spatializer#setSpatializerEnabled(boolean) */
     public void setSpatializerEnabled(boolean enabled) {
         enforceModifyDefaultAudioEffectsPermission();
-        mSpatializerHelper.setEnabled(enabled);
+        mSpatializerHelper.setFeatureEnabled(enabled);
     }
 
     /** @see Spatializer#canBeSpatialized() */
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 6d56780..ac212ee 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -17,6 +17,7 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioSystem;
@@ -24,6 +25,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,6 +62,9 @@
     private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>>
             mDevicesForAttrCache;
     private int[] mMethodCacheHit;
+    private static final Object sRoutingListenerLock = new Object();
+    @GuardedBy("sRoutingListenerLock")
+    private static @Nullable OnRoutingUpdatedListener sRoutingListener;
 
     /**
      * should be false except when trying to debug caching errors. When true, the value retrieved
@@ -76,6 +82,23 @@
             Log.d(TAG, "---- onRoutingUpdated (from native) ----------");
         }
         invalidateRoutingCache();
+        final OnRoutingUpdatedListener listener;
+        synchronized (sRoutingListenerLock) {
+            listener = sRoutingListener;
+        }
+        if (listener != null) {
+            listener.onRoutingUpdatedFromNative();
+        }
+    }
+
+    interface OnRoutingUpdatedListener {
+        void onRoutingUpdatedFromNative();
+    }
+
+    static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) {
+        synchronized (sRoutingListenerLock) {
+            sRoutingListener = listener;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 708d9e1..32ac785 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -17,9 +17,13 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioFormat;
+import android.media.AudioSystem;
+import android.media.INativeSpatializerCallback;
+import android.media.ISpatializer;
 import android.media.ISpatializerCallback;
 import android.media.Spatializer;
 import android.os.RemoteCallbackList;
@@ -28,6 +32,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * A helper class to manage Spatializer related functionality
@@ -35,12 +40,175 @@
 public class SpatializerHelper {
 
     private static final String TAG = "AS.SpatializerHelper";
+    private static final boolean DEBUG = true;
+
+    private static void logd(String s) {
+        if (DEBUG) {
+            Log.i(TAG, s);
+        }
+    }
+
+    private final @NonNull AudioSystemAdapter mASA;
+    private final @NonNull AudioService mAudioService;
+
+    //------------------------------------------------------------
+    // Spatializer state machine
+    private static final int STATE_UNINITIALIZED = 0;
+    private static final int STATE_NOT_SUPPORTED = 1;
+    private static final int STATE_DISABLED_UNAVAILABLE = 3;
+    private static final int STATE_ENABLED_UNAVAILABLE = 4;
+    private static final int STATE_ENABLED_AVAILABLE = 5;
+    private static final int STATE_DISABLED_AVAILABLE = 6;
+    private int mState = STATE_UNINITIALIZED;
+
+    /** current level as reported by native Spatializer in callback */
+    private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+    private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+    private @Nullable ISpatializer mSpat;
+    private @Nullable SpatializerCallback mSpatCallback;
+
+    // default attributes and format that determine basic availability of spatialization
+    private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_MEDIA)
+            .build();
+    private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder()
+            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+            .setSampleRate(48000)
+            .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
+            .build();
+    // device array to store the routing for the default attributes and format, size 1 because
+    // media is never expected to be duplicated
+    private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1];
 
     //---------------------------------------------------------------
     // audio device compatibility / enabled
 
     private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0);
 
+    //------------------------------------------------------
+    // initialization
+    SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) {
+        mAudioService = mother;
+        mASA = asa;
+    }
+
+    synchronized void init() {
+        Log.i(TAG, "Initializing");
+        if (mState != STATE_UNINITIALIZED) {
+            throw new IllegalStateException(("init() called in state:" + mState));
+        }
+        // is there a spatializer?
+        mSpatCallback = new SpatializerCallback();
+        final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback);
+        if (spat == null) {
+            Log.i(TAG, "init(): No Spatializer found");
+            mState = STATE_NOT_SUPPORTED;
+            return;
+        }
+        // capabilities of spatializer?
+        try {
+            byte[] levels = spat.getSupportedLevels();
+            if (levels == null
+                    || levels.length == 0
+                    || (levels.length == 1
+                    && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) {
+                Log.e(TAG, "Spatializer is useless");
+                mState = STATE_NOT_SUPPORTED;
+                return;
+            }
+            for (byte level : levels) {
+                logd("found support for level: " + level);
+                if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) {
+                    logd("Setting Spatializer to LEVEL_MULTICHANNEL");
+                    mCapableSpatLevel = level;
+                    break;
+                }
+            }
+        } catch (RemoteException e) {
+            /* capable level remains at NONE*/
+        } finally {
+            if (spat != null) {
+                try {
+                    spat.release();
+                } catch (RemoteException e) { /* capable level remains at NONE*/ }
+            }
+        }
+        if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            mState = STATE_NOT_SUPPORTED;
+            return;
+        }
+        mState = STATE_DISABLED_UNAVAILABLE;
+        // note at this point mSpat is still not instantiated
+    }
+
+    /**
+     * Like init() but resets the state and spatializer levels
+     * @param featureEnabled
+     */
+    synchronized void reset(boolean featureEnabled) {
+        Log.i(TAG, "Resetting");
+        mState = STATE_UNINITIALIZED;
+        mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+        mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+        init();
+        setFeatureEnabled(featureEnabled);
+    }
+
+    //------------------------------------------------------
+    // routing monitoring
+    void onRoutingUpdated() {
+        switch (mState) {
+            case STATE_UNINITIALIZED:
+            case STATE_NOT_SUPPORTED:
+                return;
+            case STATE_DISABLED_UNAVAILABLE:
+            case STATE_ENABLED_UNAVAILABLE:
+            case STATE_ENABLED_AVAILABLE:
+            case STATE_DISABLED_AVAILABLE:
+                break;
+        }
+        mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
+        final boolean able =
+                AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
+        logd("onRoutingUpdated: can spatialize media 5.1:" + able
+                + " on device:" + ROUTING_DEVICES[0]);
+        setDispatchAvailableState(able);
+    }
+
+    //------------------------------------------------------
+    // spatializer callback from native
+    private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
+
+        public void onLevelChanged(byte level) {
+            logd("SpatializerCallback.onLevelChanged level:" + level);
+            synchronized (SpatializerHelper.this) {
+                mSpatLevel = level;
+            }
+            // TODO use reported spat level to change state
+        }
+
+        public void onHeadTrackingModeChanged(byte mode)  {
+            logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + mode);
+        }
+
+        public void onHeadToSoundStagePoseUpdated(float[] headToStage)  {
+            if (headToStage == null) {
+                Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform");
+                return;
+            }
+            if (DEBUG) {
+                // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
+                StringBuilder t = new StringBuilder(42);
+                for (float val : headToStage) {
+                    t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]");
+                }
+                logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t);
+            }
+        }
+    };
+
+    //------------------------------------------------------
+    // compatible devices
     /**
      * @return a shallow copy of the list of compatible audio devices
      */
@@ -59,37 +227,72 @@
     }
 
     //------------------------------------------------------
-    // enabled state
-
-    // global state of feature
-    boolean mFeatureEnabled = false;
-    // initialized state, checked after each audio_server start
-    boolean mInitialized = false;
+    // states
 
     synchronized boolean isEnabled() {
-        return mFeatureEnabled;
+        switch (mState) {
+            case STATE_UNINITIALIZED:
+            case STATE_NOT_SUPPORTED:
+            case STATE_DISABLED_UNAVAILABLE:
+            case STATE_DISABLED_AVAILABLE:
+                return false;
+            case STATE_ENABLED_UNAVAILABLE:
+            case STATE_ENABLED_AVAILABLE:
+            default:
+                return true;
+        }
     }
 
     synchronized boolean isAvailable() {
-        if (!mInitialized) {
-            return false;
-        }
-        // TODO check device compatibility
-        // ...
-        return true;
-    }
-
-    synchronized void setEnabled(boolean enabled) {
-        final boolean oldState = mFeatureEnabled;
-        mFeatureEnabled = enabled;
-        if (oldState != enabled) {
-            dispatchEnabledState();
+        switch (mState) {
+            case STATE_UNINITIALIZED:
+            case STATE_NOT_SUPPORTED:
+            case STATE_ENABLED_UNAVAILABLE:
+            case STATE_DISABLED_UNAVAILABLE:
+                return false;
+            case STATE_DISABLED_AVAILABLE:
+            case STATE_ENABLED_AVAILABLE:
+            default:
+                return true;
         }
     }
 
-    public int getImmersiveAudioLevel() {
-        // TODO replace placeholder code with actual effect discovery
-        return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+    synchronized void setFeatureEnabled(boolean enabled) {
+        switch (mState) {
+            case STATE_UNINITIALIZED:
+                if (enabled) {
+                    throw(new IllegalStateException("Can't enable when uninitialized"));
+                }
+                return;
+            case STATE_NOT_SUPPORTED:
+                if (enabled) {
+                    Log.e(TAG, "Can't enable when unsupported");
+                }
+                return;
+            case STATE_DISABLED_UNAVAILABLE:
+            case STATE_DISABLED_AVAILABLE:
+                if (enabled) {
+                    createSpat();
+                    break;
+                } else {
+                    // already in disabled state
+                    return;
+                }
+            case STATE_ENABLED_UNAVAILABLE:
+            case STATE_ENABLED_AVAILABLE:
+                if (!enabled) {
+                    releaseSpat();
+                    break;
+                } else {
+                    // already in enabled state
+                    return;
+                }
+        }
+        setDispatchFeatureEnabledState(enabled);
+    }
+
+    synchronized int getCapableImmersiveAudioLevel() {
+        return mCapableSpatLevel;
     }
 
     final RemoteCallbackList<ISpatializerCallback> mStateCallbacks =
@@ -105,12 +308,94 @@
         mStateCallbacks.unregister(callback);
     }
 
-    private synchronized void dispatchEnabledState() {
+    /**
+     * precondition: mState = STATE_*
+     *               isFeatureEnabled() != featureEnabled
+     * @param featureEnabled
+     */
+    private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) {
+        if (featureEnabled) {
+            switch (mState) {
+                case STATE_DISABLED_UNAVAILABLE:
+                    mState = STATE_ENABLED_UNAVAILABLE;
+                    break;
+                case STATE_DISABLED_AVAILABLE:
+                    mState = STATE_ENABLED_AVAILABLE;
+                    break;
+                default:
+                    throw(new IllegalStateException("Invalid mState:" + mState
+                            + " for enabled true"));
+            }
+        } else {
+            switch (mState) {
+                case STATE_ENABLED_UNAVAILABLE:
+                    mState = STATE_DISABLED_UNAVAILABLE;
+                    break;
+                case STATE_ENABLED_AVAILABLE:
+                    mState = STATE_DISABLED_AVAILABLE;
+                    break;
+                default:
+                    throw (new IllegalStateException("Invalid mState:" + mState
+                            + " for enabled false"));
+            }
+        }
         final int nbCallbacks = mStateCallbacks.beginBroadcast();
         for (int i = 0; i < nbCallbacks; i++) {
             try {
                 mStateCallbacks.getBroadcastItem(i)
-                        .dispatchSpatializerEnabledChanged(mFeatureEnabled);
+                        .dispatchSpatializerEnabledChanged(featureEnabled);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
+            }
+        }
+        mStateCallbacks.finishBroadcast();
+        // TODO persist enabled state
+    }
+
+    private synchronized void setDispatchAvailableState(boolean available) {
+        switch (mState) {
+            case STATE_UNINITIALIZED:
+            case STATE_NOT_SUPPORTED:
+                throw(new IllegalStateException(
+                        "Should not update available state in state:" + mState));
+            case STATE_DISABLED_UNAVAILABLE:
+                if (available) {
+                    mState = STATE_DISABLED_AVAILABLE;
+                    break;
+                } else {
+                    // already in unavailable state
+                    return;
+                }
+            case STATE_ENABLED_UNAVAILABLE:
+                if (available) {
+                    mState = STATE_ENABLED_AVAILABLE;
+                    break;
+                } else {
+                    // already in unavailable state
+                    return;
+                }
+            case STATE_DISABLED_AVAILABLE:
+                if (available) {
+                    // already in available state
+                    return;
+                } else {
+                    mState = STATE_DISABLED_UNAVAILABLE;
+                    break;
+                }
+            case STATE_ENABLED_AVAILABLE:
+                if (available) {
+                    // already in available state
+                    return;
+                } else {
+                    mState = STATE_ENABLED_UNAVAILABLE;
+                    break;
+                }
+        }
+        final int nbCallbacks = mStateCallbacks.beginBroadcast();
+        for (int i = 0; i < nbCallbacks; i++) {
+            try {
+                mStateCallbacks.getBroadcastItem(i)
+                        .dispatchSpatializerAvailableChanged(available);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
             }
@@ -119,10 +404,72 @@
     }
 
     //------------------------------------------------------
+    // native Spatializer management
+
+    /**
+     * precondition: mState == STATE_DISABLED_*
+     */
+    private void createSpat() {
+        if (mSpat == null) {
+            mSpatCallback = new SpatializerCallback();
+            mSpat = AudioSystem.getSpatializer(mSpatCallback);
+            try {
+                mSpat.setLevel((byte)  Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't set spatializer level", e);
+                mState = STATE_NOT_SUPPORTED;
+                mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+            }
+        }
+    }
+
+    /**
+     * precondition: mState == STATE_ENABLED_*
+     */
+    private void releaseSpat() {
+        if (mSpat != null) {
+            mSpatCallback = null;
+            try {
+                mSpat.release();
+                mSpat = null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't set release spatializer cleanly", e);
+            }
+        }
+    }
+
+    //------------------------------------------------------
     // virtualization capabilities
     synchronized boolean canBeSpatialized(
             @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
-        // TODO hook up to spatializer effect for query
-        return false;
+        logd("canBeSpatialized usage:" + attributes.getUsage()
+                + " format:" + format.toLogFriendlyString());
+        switch (mState) {
+            case STATE_UNINITIALIZED:
+            case STATE_NOT_SUPPORTED:
+            case STATE_ENABLED_UNAVAILABLE:
+            case STATE_DISABLED_UNAVAILABLE:
+                logd("canBeSpatialized false due to state:" + mState);
+                return false;
+            case STATE_DISABLED_AVAILABLE:
+            case STATE_ENABLED_AVAILABLE:
+                break;
+        }
+
+        // filter on AudioAttributes usage
+        switch (attributes.getUsage()) {
+            case AudioAttributes.USAGE_MEDIA:
+            case AudioAttributes.USAGE_GAME:
+                break;
+            default:
+                logd("canBeSpatialized false due to usage:" + attributes.getUsage());
+                return false;
+        }
+        AudioDeviceAttributes[] devices =
+                // going through adapter to take advantage of routing cache
+                (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+        final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
+        logd("canBeSpatialized returning " + able);
+        return able;
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 73bcea6..827523b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2252,6 +2252,9 @@
                         int displayId = msg.arg1;
                         final LogicalDisplay display =
                                 mLogicalDisplayMapper.getDisplayLocked(displayId);
+                        if (display == null) {
+                            break;
+                        }
                         uids = display.getPendingFrameRateOverrideUids();
                         display.clearPendingFrameRateOverrideUids();
                     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a98a4ec..a124f76 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4945,10 +4945,12 @@
      *                this has become invisible.
      */
     private void postApplyAnimation(boolean visible) {
+        final boolean usingShellTransitions =
+                mAtmService.getTransitionController().getTransitionPlayer() != null;
         final boolean delayed = isAnimating(PARENTS | CHILDREN,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
                         | ANIMATION_TYPE_RECENTS);
-        if (!delayed) {
+        if (!delayed && !usingShellTransitions) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
             // which we might have done because we were in closing/opening apps list.
@@ -4967,8 +4969,8 @@
         // updated.
         // If we're becoming invisible, update the client visibility if we are not running an
         // animation. Otherwise, we'll update client visibility in onAnimationFinished.
-        if (visible || !isAnimating(PARENTS,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+        if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+                || usingShellTransitions) {
             setClientVisible(visible);
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3b43e48..c721c24 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -30,7 +30,6 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 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;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -1573,11 +1572,7 @@
             newTransition.setRemoteTransition(remoteTransition);
         }
         mService.getTransitionController().collect(r);
-        // TODO(b/188669821): Remove when navbar reparenting moves to shell
-        if (r.getActivityType() == ACTIVITY_TYPE_HOME && r.getOptions() != null
-                && r.getOptions().getTransientLaunch()) {
-            mService.getTransitionController().setIsLegacyRecents();
-        }
+        final boolean isTransient = r.getOptions() != null && r.getOptions().getTransientLaunch();
         try {
             mService.deferWindowLayout();
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
@@ -1625,6 +1620,11 @@
                     // it as an existence change.
                     mService.getTransitionController().collectExistenceChange(r);
                 }
+                if (isTransient) {
+                    // `r` isn't guaranteed to be the actual relevant activity, so we must wait
+                    // until after we launched to identify the relevant activity.
+                    mService.getTransitionController().setTransientLaunch(mLastStartActivityRecord);
+                }
                 if (newTransition != null) {
                     mService.getTransitionController().requestStartTransition(newTransition,
                             mTargetTask, remoteTransition);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 5174a38..0ba77d8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -30,6 +30,7 @@
 import android.content.res.CompatibilityInfo;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.RemoteException;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.IntArray;
@@ -611,6 +612,14 @@
         PackageConfigurationUpdater setNightMode(int nightMode);
 
         /**
+         * Sets the app-specific locales for the application referenced by this updater.
+         * This setting is persisted and will overlay on top of the system locales for
+         * the said application.
+         * @return the current {@link PackageConfigurationUpdater} updated with the provided locale.
+         */
+        PackageConfigurationUpdater setLocales(LocaleList locales);
+
+        /**
          * Commit changes.
          */
         void commit();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 859107c..1c8f6f1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -956,7 +956,7 @@
         setRecentTasks(new RecentTasks(this, mTaskSupervisor));
         mVrController = new VrController(mGlobalLock);
         mKeyguardController = mTaskSupervisor.getKeyguardController();
-        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
+        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue, this);
     }
 
     public void onActivityManagerInternalAdded() {
@@ -6575,7 +6575,8 @@
     final class PackageConfigurationUpdaterImpl implements
             ActivityTaskManagerInternal.PackageConfigurationUpdater {
         private final int mPid;
-        private int mNightMode;
+        private Integer mNightMode;
+        private LocaleList mLocales;
 
         PackageConfigurationUpdaterImpl(int pid) {
             mPid = pid;
@@ -6588,6 +6589,13 @@
         }
 
         @Override
+        public ActivityTaskManagerInternal.PackageConfigurationUpdater
+                setLocales(LocaleList locales) {
+            mLocales = locales;
+            return this;
+        }
+
+        @Override
         public void commit() {
             synchronized (mGlobalLock) {
                 final long ident = Binder.clearCallingIdentity();
@@ -6597,8 +6605,10 @@
                         Slog.w(TAG, "Override application configuration: cannot find pid " + mPid);
                         return;
                     }
-                    wpc.setOverrideNightMode(mNightMode);
-                    wpc.updateNightModeForAllActivities(mNightMode);
+                    LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists(
+                            mLocales, getGlobalConfiguration().getLocales());
+                    wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+                    wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride);
                     mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -6606,8 +6616,12 @@
             }
         }
 
-        int getNightMode() {
+        Integer getNightMode() {
             return mNightMode;
         }
+
+        LocaleList getLocales() {
+            return mLocales;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 6fafc02..eeb85c5 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -39,6 +39,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.LocaleList;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -512,7 +513,7 @@
         return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
     }
 
-    /** Returns the activity type associated with the the configuration container. */
+    /** Returns the activity type associated with the configuration container. */
     /*@WindowConfiguration.ActivityType*/
     public int getActivityType() {
         return mFullConfiguration.windowConfiguration.getActivityType();
@@ -546,20 +547,48 @@
     }
 
     /**
+     * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
+     * @return true if any of the requested configuration has been updated.
+     */
+    public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+        boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
+                nightMode);
+        boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
+                locales);
+        if (newNightModeSet || newLocalesSet) {
+            onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+        }
+        return newNightModeSet || newLocalesSet;
+    }
+
+    /**
      * Overrides the night mode applied to this ConfigurationContainer.
      * @return true if the nightMode has been changed.
      */
-    public boolean setOverrideNightMode(int nightMode) {
+    private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) {
         final int currentUiMode = mRequestedOverrideConfiguration.uiMode;
         final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK;
         final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
         if (currentNightMode == validNightMode) {
             return false;
         }
-        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
-        mRequestsTmpConfig.uiMode = validNightMode
+        requestsTmpConfig.uiMode = validNightMode
                 | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
-        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+        return true;
+    }
+
+    /**
+     * Overrides the locales applied to this ConfigurationContainer.
+     * @return true if the LocaleList has been changed.
+     */
+    private boolean setOverrideLocales(Configuration requestsTmpConfig,
+            @NonNull LocaleList overrideLocales) {
+        if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) {
+            return false;
+        }
+        requestsTmpConfig.setLocales(overrideLocales);
+        requestsTmpConfig.userSetLocale = true;
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 199159e..63f6387 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -578,6 +578,7 @@
      * Specifies the count to determine whether to defer updating the IME target until ready.
      */
     private int mDeferUpdateImeTargetCount;
+    private boolean mUpdateImeRequestedWhileDeferred;
 
     private MagnificationSpec mMagnificationSpec;
 
@@ -3729,6 +3730,7 @@
         final WindowState curTarget = mImeLayeringTarget;
         if (!canUpdateImeTarget()) {
             if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target");
+            mUpdateImeRequestedWhileDeferred = true;
             return curTarget;
         }
 
@@ -4991,6 +4993,9 @@
      * Increment the deferral count to determine whether to update the IME target.
      */
     void deferUpdateImeTarget() {
+        if (mDeferUpdateImeTargetCount == 0) {
+            mUpdateImeRequestedWhileDeferred = false;
+        }
         mDeferUpdateImeTargetCount++;
     }
 
@@ -5004,7 +5009,7 @@
         }
 
         mDeferUpdateImeTargetCount--;
-        if (mDeferUpdateImeTargetCount == 0) {
+        if (mDeferUpdateImeTargetCount == 0 && mUpdateImeRequestedWhileDeferred) {
             computeImeTarget(true /* updateImeTarget */);
         }
     }
diff --git a/services/core/java/com/android/server/wm/LocaleOverlayHelper.java b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
new file mode 100644
index 0000000..a1a01db
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
@@ -0,0 +1,62 @@
+/*
+ * 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.server.wm;
+
+import android.os.LocaleList;
+
+import java.util.Locale;
+
+/**
+ * Static utilities to overlay locales on top of another localeList.
+ *
+ * <p>This is used to overlay application-specific locales in
+ *  {@link com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater} on top of
+ *  system locales.
+ */
+final class LocaleOverlayHelper {
+
+    /**
+     * Combines the overlay locales and base locales.
+     * @return the combined {@link LocaleList} if the overlay locales is not empty/null else
+     * returns the empty/null LocaleList.
+     */
+    static LocaleList combineLocalesIfOverlayExists(LocaleList overlayLocales,
+            LocaleList baseLocales) {
+        if (overlayLocales == null || overlayLocales.isEmpty()) {
+            return overlayLocales;
+        }
+        return combineLocales(overlayLocales, baseLocales);
+    }
+
+    /**
+     * Creates a combined {@link LocaleList} by placing overlay locales before base locales and
+     * dropping duplicates from the base locales.
+     */
+    private static LocaleList combineLocales(LocaleList overlayLocales, LocaleList baseLocales) {
+        Locale[] combinedLocales = new Locale[overlayLocales.size() + baseLocales.size()];
+        for (int i = 0; i < overlayLocales.size(); i++) {
+            combinedLocales[i] = overlayLocales.get(i);
+        }
+        for (int i = 0; i < baseLocales.size(); i++) {
+            combinedLocales[i + overlayLocales.size()] = baseLocales.get(i);
+        }
+        // Constructor of {@link LocaleList} removes duplicates
+        return new LocaleList(combinedLocales);
+    }
+
+
+}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 1552a96..505c4beb 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.os.Environment;
+import android.os.LocaleList;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -54,12 +55,14 @@
     private static final String TAG_CONFIG = "config";
     private static final String ATTR_PACKAGE_NAME = "package_name";
     private static final String ATTR_NIGHT_MODE = "night_mode";
+    private static final String ATTR_LOCALES = "locale_list";
 
     private static final String PACKAGE_DIRNAME = "package_configs";
     private static final String SUFFIX_FILE_NAME = "_config.xml";
 
     private final PersisterQueue mPersisterQueue;
     private final Object mLock = new Object();
+    private final ActivityTaskManagerService mAtm;
 
     @GuardedBy("mLock")
     private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
@@ -72,8 +75,9 @@
         return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
     }
 
-    PackageConfigPersister(PersisterQueue queue) {
+    PackageConfigPersister(PersisterQueue queue, ActivityTaskManagerService atm) {
         mPersisterQueue = queue;
+        mAtm = atm;
     }
 
     @GuardedBy("mLock")
@@ -100,7 +104,8 @@
                     final TypedXmlPullParser in = Xml.resolvePullParser(is);
                     int event;
                     String packageName = null;
-                    int nightMode = MODE_NIGHT_AUTO;
+                    Integer nightMode = null;
+                    LocaleList locales = null;
                     while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
                             && event != XmlPullParser.END_TAG) {
                         final String name = in.getName();
@@ -120,6 +125,9 @@
                                         case ATTR_NIGHT_MODE:
                                             nightMode = Integer.parseInt(attrValue);
                                             break;
+                                        case ATTR_LOCALES:
+                                            locales = LocaleList.forLanguageTags(attrValue);
+                                            break;
                                     }
                                 }
                             }
@@ -130,6 +138,7 @@
                         final PackageConfigRecord initRecord =
                                 findRecordOrCreate(mModified, packageName, userId);
                         initRecord.mNightMode = nightMode;
+                        initRecord.mLocales = locales;
                         if (DEBUG) {
                             Slog.d(TAG, "loadPackages: load one package " + initRecord);
                         }
@@ -155,7 +164,9 @@
                         "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
             }
             if (modifiedRecord != null) {
-                container.setOverrideNightMode(modifiedRecord.mNightMode);
+                container.applyAppSpecificConfig(modifiedRecord.mNightMode,
+                        LocaleOverlayHelper.combineLocalesIfOverlayExists(
+                        modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
             }
         }
     }
@@ -165,10 +176,16 @@
             ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
         synchronized (mLock) {
             PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
-            record.mNightMode = impl.getNightMode();
-
-            if (record.isResetNightMode()) {
-                removePackage(record.mName, record.mUserId);
+            if (impl.getNightMode() != null) {
+                record.mNightMode = impl.getNightMode();
+            }
+            if (impl.getLocales() != null) {
+                record.mLocales = impl.getLocales();
+            }
+            if ((record.mNightMode == null || record.isResetNightMode())
+                    && (record.mLocales == null || record.mLocales.isEmpty())) {
+                // if all values default to system settings, we can remove the package.
+                removePackage(packageName, userId);
             } else {
                 final PackageConfigRecord pendingRecord =
                         findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -179,10 +196,11 @@
                 } else {
                     writeRecord = pendingRecord;
                 }
-                if (writeRecord.mNightMode == record.mNightMode) {
+
+                if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
                     return;
                 }
-                writeRecord.mNightMode = record.mNightMode;
+
                 if (DEBUG) {
                     Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
                 }
@@ -191,6 +209,22 @@
         }
     }
 
+    private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+        if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+            return false;
+        }
+        writeRecord.mNightMode = record.mNightMode;
+        return true;
+    }
+
+    private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+        if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+            return false;
+        }
+        writeRecord.mLocales = record.mLocales;
+        return true;
+    }
+
     @GuardedBy("mLock")
     void removeUser(int userId) {
         synchronized (mLock) {
@@ -210,7 +244,7 @@
     @GuardedBy("mLock")
     void onPackageUninstall(String packageName) {
         synchronized (mLock) {
-            for (int i = mModified.size() - 1; i > 0; i--) {
+            for (int i = mModified.size() - 1; i >= 0; i--) {
                 final int userId = mModified.keyAt(i);
                 removePackage(packageName, userId);
             }
@@ -242,7 +276,8 @@
     static class PackageConfigRecord {
         final String mName;
         final int mUserId;
-        int mNightMode;
+        Integer mNightMode;
+        LocaleList mLocales;
 
         PackageConfigRecord(String name, int userId) {
             mName = name;
@@ -256,7 +291,7 @@
         @Override
         public String toString() {
             return "PackageConfigRecord package name: " + mName + " userId " + mUserId
-                    + " nightMode " + mNightMode;
+                    + " nightMode " + mNightMode + " locales " + mLocales;
         }
     }
 
@@ -369,7 +404,13 @@
             }
             xmlSerializer.startTag(null, TAG_CONFIG);
             xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
-            xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+            if (mRecord.mNightMode != null) {
+                xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+            }
+            if (mRecord.mLocales != null) {
+                xmlSerializer.attribute(null, ATTR_LOCALES, mRecord.mLocales
+                        .toLanguageTags());
+            }
             xmlSerializer.endTag(null, TAG_CONFIG);
             xmlSerializer.endDocument();
             xmlSerializer.flush();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6b93364..1a46d0f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -148,6 +148,9 @@
      */
     private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
 
+    /** Set of transient activities (lifecycle initially tied to this transition). */
+    private ArraySet<ActivityRecord> mTransientLaunches = null;
+
     /** Custom activity-level animation options and callbacks. */
     private TransitionInfo.AnimationOptions mOverrideOptions;
     private IRemoteCallback mClientAnimationStartCallback = null;
@@ -174,6 +177,20 @@
         mFlags |= flag;
     }
 
+    /** Records an activity as transient-launch. This activity must be already collected. */
+    void setTransientLaunch(@NonNull ActivityRecord activity) {
+        if (mTransientLaunches == null) {
+            mTransientLaunches = new ArraySet<>();
+        }
+        mTransientLaunches.add(activity);
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+                + "transient-launch", mSyncId, activity);
+    }
+
+    boolean isTransientLaunch(@NonNull ActivityRecord activity) {
+        return mTransientLaunches != null && mTransientLaunches.contains(activity);
+    }
+
     @VisibleForTesting
     int getSyncId() {
         return mSyncId;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 69e6a54..c1d0f80 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -185,6 +186,20 @@
         return false;
     }
 
+    /**
+     * @return {@code true} if {@param ar} is part of a transient-launch activity in an active
+     * transition.
+     */
+    boolean isTransientLaunch(@NonNull ActivityRecord ar) {
+        if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) {
+            return true;
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
+        }
+        return false;
+    }
+
     @WindowManager.TransitionType
     int getCollectingTransitionType() {
         return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
@@ -331,13 +346,18 @@
     }
 
     /**
-     * Explicitly mark the collectingTransition as being part of recents gesture. Used for legacy
-     * behaviors.
-     * TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+     * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently
+     * tied to the transition).
      */
-    void setIsLegacyRecents() {
+    void setTransientLaunch(@NonNull ActivityRecord activity) {
         if (mCollectingTransition == null) return;
-        mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+        mCollectingTransition.setTransientLaunch(activity);
+
+        // TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+        // Also interpret HOME transient launch as recents
+        if (activity.getActivityType() == ACTIVITY_TYPE_HOME) {
+            mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+        }
     }
 
     void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 232c283..907098e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7732,7 +7732,7 @@
                 final WindowState currentFocus = displayContent.mCurrentFocus;
                 if (currentFocus != null && currentFocus.mSession.mUid == uid
                         && currentFocus.mSession.mPid == pid) {
-                    return true;
+                    return currentFocus.canBeImeTarget();
                 }
             }
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index cd29f0e..6eb2e8a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -57,6 +57,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -817,10 +818,13 @@
         return false;
     }
 
-    void updateNightModeForAllActivities(int nightMode) {
+    // TODO(b/199277065): Re-assess how app-specific locales are applied based on UXR
+    // TODO(b/199277729): Consider whether we need to add special casing for edge cases like
+    //  activity-embeddings etc.
+    void updateAppSpecificSettingsForAllActivities(Integer nightMode, LocaleList localesOverride) {
         for (int i = mActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = mActivities.get(i);
-            if (r.setOverrideNightMode(nightMode) && r.mVisibleRequested) {
+            if (r.applyAppSpecificConfig(nightMode, localesOverride) && r.mVisibleRequested) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 22db297..2ccbf40 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -372,6 +372,14 @@
     private boolean mRedrawForSyncReported;
 
     /**
+     * {@code true} when the client was still drawing for sync when the sync-set was finished or
+     * cancelled. This can happen if the window goes away during a sync. In this situation we need
+     * to make sure to still apply the postDrawTransaction when it finishes to prevent the client
+     * from getting stuck in a bad state.
+     */
+    boolean mClientWasDrawingForSync = false;
+
+    /**
      * Special mode that is intended only for the rounded corner overlay: during rotation
      * transition, we un-rotate the window token such that the window appears as it did before the
      * rotation.
@@ -1980,7 +1988,7 @@
         final ActivityRecord atoken = mActivityRecord;
         return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
                 && isVisibleByPolicy() && !isParentWindowHidden()
-                && (atoken == null || atoken.mVisibleRequested)
+                && (atoken == null || atoken.isVisible())
                 && !mAnimatingExit && !mDestroying;
     }
 
@@ -2706,6 +2714,13 @@
             }
         }
 
+        // Don't allow transient-launch activities to take IME.
+        if (rootTask != null && mActivityRecord != null
+                && mWmService.mAtmService.getTransitionController().isTransientLaunch(
+                        mActivityRecord)) {
+            return false;
+        }
+
         if (DEBUG_INPUT_METHOD) {
             Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
             if (!isVisibleOrAdding()) {
@@ -6002,6 +6017,14 @@
         return super.isSyncFinished();
     }
 
+    @Override
+    void finishSync(Transaction outMergedTransaction, boolean cancel) {
+        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
+            mClientWasDrawingForSync = true;
+        }
+        super.finishSync(outMergedTransaction, cancel);
+    }
+
     boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
         if (mOrientationChangeRedrawRequestTime > 0) {
             final long duration =
@@ -6017,8 +6040,11 @@
         }
 
         executeDrawHandlers(postDrawTransaction);
+
+        final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null;
+        mClientWasDrawingForSync = false;
         if (!onSyncFinishedDrawing()) {
-            return mWinAnimator.finishDrawingLocked(postDrawTransaction);
+            return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow);
         }
 
         if (mActivityRecord != null
@@ -6032,7 +6058,7 @@
             mSyncTransaction.merge(postDrawTransaction);
         }
 
-        mWinAnimator.finishDrawingLocked(null);
+        mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */);
         // We always want to force a traversal after a finish draw for blast sync.
         return true;
     }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index f25706a..a0dc247 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -228,7 +228,8 @@
         }
     }
 
-    boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
+    boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
+            boolean forceApplyNow) {
         final boolean startingWindow =
                 mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
         if (startingWindow) {
@@ -253,11 +254,11 @@
             // If there is no surface, the last draw was for the previous surface. We don't want to
             // wait until the new surface is shown and instead just apply the transaction right
             // away.
-            if (mLastHidden && mDrawState != NO_SURFACE) {
+            if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) {
                 mPostDrawTransaction.merge(postDrawTransaction);
                 layoutNeeded = true;
             } else {
-                postDrawTransaction.apply();
+                mWin.getSyncTransaction().merge(postDrawTransaction);
             }
         }
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a94ad4a..bb9740b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,7 +337,7 @@
     void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
     bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
-    void setPointerCapture(bool enabled) override;
+    void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
 
     /* --- PointerControllerPolicyInterface implementation --- */
@@ -372,8 +372,8 @@
         // Show touches feature enable/disable.
         bool showTouches;
 
-        // Pointer capture feature enable/disable.
-        bool pointerCapture;
+        // The latest request to enable or disable Pointer Capture.
+        PointerCaptureRequest pointerCaptureRequest;
 
         // Sprite controller singleton, created on first use.
         sp<SpriteController> spriteController;
@@ -417,7 +417,6 @@
         mLocked.pointerSpeed = 0;
         mLocked.pointerGesturesEnabled = true;
         mLocked.showTouches = false;
-        mLocked.pointerCapture = false;
         mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
     }
     mInteractive = true;
@@ -446,7 +445,9 @@
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                 toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
-        dump += StringPrintf(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
+        dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
+                             mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+                             mLocked.pointerCaptureRequest.seq);
     }
     dump += "\n";
 
@@ -634,7 +635,7 @@
 
         outConfig->showTouches = mLocked.showTouches;
 
-        outConfig->pointerCapture = mLocked.pointerCapture;
+        outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
 
         outConfig->setDisplayViewports(mLocked.viewports);
 
@@ -1383,16 +1384,16 @@
     checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
 }
 
-void NativeInputManager::setPointerCapture(bool enabled) {
+void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) {
     { // acquire lock
         AutoMutex _l(mLock);
 
-        if (mLocked.pointerCapture == enabled) {
+        if (mLocked.pointerCaptureRequest == request) {
             return;
         }
 
-        ALOGV("%s pointer capture.", enabled ? "Enabling" : "Disabling");
-        mLocked.pointerCapture = enabled;
+        ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+        mLocked.pointerCaptureRequest = request;
     } // release lock
 
     mInputManager->getReader()->requestRefreshConfiguration(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 8ea21ec..a301799 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -46,6 +46,12 @@
     @GuardedBy("mLock")
     private final SparseIntArray mPermissionPolicy = new SparseIntArray();
 
+    /** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}.
+     *
+     * <p>For users affiliated with the device, they inherit the policy from {@code DO} so
+     * it will map to the {@code DO}'s policy. Otherwise it will map to the admin of the requesting
+     * user.
+     */
     @GuardedBy("mLock")
     private final SparseBooleanArray mCanGrantSensorsPermissions = new SparseBooleanArray();
 
@@ -102,17 +108,16 @@
     }
 
     @Override
-    public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle) {
+    public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userId) {
         synchronized (mLock) {
-            return mCanGrantSensorsPermissions.get(userHandle, false);
+            return mCanGrantSensorsPermissions.get(userId, false);
         }
     }
 
     /** Sets ahmin control over permission grants for user. */
-    public void setAdminCanGrantSensorsPermissions(@UserIdInt int userHandle,
-            boolean canGrant) {
+    public void setAdminCanGrantSensorsPermissions(@UserIdInt int userId, boolean canGrant) {
         synchronized (mLock) {
-            mCanGrantSensorsPermissions.put(userHandle, canGrant);
+            mCanGrantSensorsPermissions.put(userId, canGrant);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 70219d2..6b4b0c94 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9144,9 +9144,7 @@
     }
 
     /**
-     * Returns the ActiveAdmin associated wit the PO or DO on the given user.
-     * @param userHandle
-     * @return
+     * Returns the ActiveAdmin associated with the PO or DO on the given user.
      */
     private @Nullable ActiveAdmin getDeviceOrProfileOwnerAdminLocked(int userHandle) {
         ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
@@ -14305,6 +14303,7 @@
             maybePauseDeviceWideLoggingLocked();
             maybeResumeDeviceWideLoggingLocked();
             maybeClearLockTaskPolicyLocked();
+            updateAdminCanGrantSensorsPermissionCache(callingUserId);
         }
     }
 
@@ -16968,6 +16967,19 @@
     }
 
     @Override
+    public void clearOrganizationIdForUser(int userHandle) {
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle);
+            owner.mOrganizationId = null;
+            owner.mEnrollmentSpecificId = null;
+            saveSettingsLocked(userHandle);
+        }
+    }
+
+    @Override
     public UserHandle createAndProvisionManagedProfile(
             @NonNull ManagedProfileProvisioningParams provisioningParams,
             @NonNull String callerPackage) {
@@ -17469,7 +17481,10 @@
         });
     }
 
-    private void setAdminCanGrantSensorsPermissionForUserUnchecked(int userId, boolean canGrant) {
+    private void setAdminCanGrantSensorsPermissionForUserUnchecked(@UserIdInt int userId,
+            boolean canGrant) {
+        Slogf.d(LOG_TAG, "setAdminCanGrantSensorsPermissionForUserUnchecked(%d, %b)",
+                userId, canGrant);
         synchronized (getLockObject()) {
             ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
 
@@ -17483,10 +17498,18 @@
         }
     }
 
-    private void updateAdminCanGrantSensorsPermissionCache(int userId) {
+    private void updateAdminCanGrantSensorsPermissionCache(@UserIdInt int userId) {
         synchronized (getLockObject()) {
-            ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
-            final boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+
+            ActiveAdmin owner;
+            // If the user is affiliated the device (either a DO itself, or an affiliated PO),
+            // use mAdminCanGrantSensorsPermissions from the DO
+            if (isUserAffiliatedWithDeviceLocked(userId)) {
+                owner = getDeviceOwnerAdminLocked();
+            } else {
+                owner = getDeviceOrProfileOwnerAdminLocked(userId);
+            }
+            boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
             mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant);
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 40a5a81..764f63d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -43,13 +43,17 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.EnterPipRequestedItem;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -61,6 +65,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
@@ -80,6 +85,9 @@
     private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
             ArgumentCaptor.forClass(ClientTransaction.class);
 
+    private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
+    private static final int DEFAULT_USER_ID = 100;
+
     @Before
     public void setUp() throws Exception {
         setBooted(mAtm);
@@ -489,5 +497,269 @@
         assertTrue(activity.supportsMultiWindow());
         assertTrue(task.supportsMultiWindow());
     }
+
+    @Test
+    public void testPackageConfigUpdate_locales_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
+
+        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_nightMode_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange.getConfiguration().getLocales());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        WindowProcessController wpc = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpc.getConfiguration().getLocales());
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+                wpc.getConfiguration().getLocales());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME);
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        WindowProcessController wpc = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpc.getConfiguration().getLocales());
+
+        packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
+                .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpc.getConfiguration().getLocales());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        mAtm.mInternal.removeUser(DEFAULT_USER_ID);
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    private WindowProcessController createWindowProcessController(String packageName,
+            int userId) {
+        WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
+        ApplicationInfo info = mock(ApplicationInfo.class);
+        info.packageName = packageName;
+        WindowProcessController wpc = new WindowProcessController(
+                mAtm, info, packageName, 0, userId, null, mMockListener);
+        wpc.setThread(mock(IApplicationThread.class));
+        return wpc;
+    }
+
 }
 
+
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d3f2d14..c56b614 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -49,6 +50,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Before;
@@ -371,8 +373,9 @@
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.mVisibleRequested = true;
-        doReturn(true).when(activity).setOverrideNightMode(anyInt());
-        mWpc.updateNightModeForAllActivities(Configuration.UI_MODE_NIGHT_YES);
+        doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+        mWpc.updateAppSpecificSettingsForAllActivities(Configuration.UI_MODE_NIGHT_YES,
+                LocaleList.forLanguageTags("en-XA"));
         verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
     }