Merge "Move shell check to ShellCommand from system-service" into main
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 9b81bd4..13d6ae5 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -50,7 +50,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.public.latest",
+            new_since: ":android.api.combined.public.latest",
             baseline_file: ":non-updatable-lint-baseline.txt",
         },
     },
@@ -130,7 +130,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.system.latest",
+            new_since: ":android.api.combined.system.latest",
             baseline_file: ":non-updatable-system-lint-baseline.txt",
         },
     },
@@ -185,7 +185,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.test.latest",
+            new_since: ":android.api.combined.test.latest",
             baseline_file: ":non-updatable-test-lint-baseline.txt",
         },
     },
@@ -269,7 +269,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.module-lib.latest",
+            new_since: ":android.api.combined.module-lib.latest",
             baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
         },
     },
diff --git a/core/api/current.txt b/core/api/current.txt
index 53cf7d5..13958d2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26592,7 +26592,7 @@
     method public long getFlags();
     method @Nullable public android.media.MediaMetadata getMetadata();
     method public String getPackageName();
-    method @Nullable public android.media.session.MediaController.PlaybackInfo getPlaybackInfo();
+    method @NonNull public android.media.session.MediaController.PlaybackInfo getPlaybackInfo();
     method @Nullable public android.media.session.PlaybackState getPlaybackState();
     method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue();
     method @Nullable public CharSequence getQueueTitle();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5dd4a7..e1cb630 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5796,6 +5796,8 @@
      * @see #onRequestPermissionsResult
      */
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+    @SuppressLint("OnNameExpected")
+    // Suppress lint as this is an overload of the original API.
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
         final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
                 : createDeviceContext(deviceId).getPackageManager();
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index dc44764..0deb842 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -378,6 +378,14 @@
                 }
             ],
             "file_patterns": ["(/|^)ContextImpl.java"]
+        },
+        {
+            "file_patterns": [
+                "(/|^)Activity.*.java",
+                "(/|^)PendingIntent.java",
+                "(/|^)ComtextImpl.java"
+            ],
+            "name": "CtsWindowManagerBackgroundActivityTestCases"
         }
     ],
     "postsubmit": [
@@ -392,14 +400,6 @@
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
             "name": "CtsAppOpsTestCases"
-        },
-        {
-            "file_patterns": [
-                "(/|^)Activity.*.java",
-                "(/|^)PendingIntent.java",
-                "(/|^)ComtextImpl.java"
-            ],
-            "name": "CtsWindowManagerBackgroundActivityTestCases"
         }
     ]
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 69f29f3..c529f7d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1698,7 +1698,7 @@
 
     /**
      * A boolean extra indicating whether device encryption can be skipped as part of
-     * <a href="#managed-provisioning>provisioning</a>.
+     * <a href="#managed-provisioning">provisioning</a>.
      *
      * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action
      * {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning.
@@ -10427,7 +10427,7 @@
     @WorkerThread
     public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
             Bundle settings) {
-        if (!Flags.dmrhCanSetAppRestriction()) {
+        if (!Flags.dmrhSetAppRestrictions()) {
             throwIfParentInstance("setApplicationRestrictions");
         }
 
@@ -11835,7 +11835,7 @@
     @WorkerThread
     public @NonNull Bundle getApplicationRestrictions(
             @Nullable ComponentName admin, String packageName) {
-        if (!Flags.dmrhCanSetAppRestriction()) {
+        if (!Flags.dmrhSetAppRestrictions()) {
             throwIfParentInstance("getApplicationRestrictions");
         }
 
@@ -14120,7 +14120,7 @@
     public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
         throwIfParentInstance("getParentProfileInstance");
         try {
-            if (Flags.dmrhCanSetAppRestriction()) {
+            if (Flags.dmrhSetAppRestrictions()) {
                 UserManager um = mContext.getSystemService(UserManager.class);
                 if (!um.isManagedProfile()) {
                     throw new SecurityException("The current user does not have a parent profile.");
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 3d6ec19..4154e66 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -244,10 +244,13 @@
 }
 
 flag {
-  name: "dmrh_can_set_app_restriction"
+  name: "dmrh_set_app_restrictions"
   namespace: "enterprise"
   description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
   bug: "328758346"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
index 99fa869..1b718d4 100644
--- a/core/java/android/app/prediction/AppPredictionContext.java
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -24,6 +24,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Class that provides contextual information about the environment in which the app prediction is
  * used, such as package name, UI in which the app targets are shown, and number of targets.
@@ -99,6 +101,13 @@
     }
 
     @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(mUiSurface, mPackageName);
+        hashCode = 31 * hashCode + mPredictedTargetCount;
+        return hashCode;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
index fef9e70..25c1a59 100644
--- a/core/java/android/app/prediction/AppTarget.java
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -167,6 +167,16 @@
     }
 
     @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(mId, mPackageName, mClassName, mUser);
+        if (mShortcutInfo != null) {
+            hashCode = 31 * hashCode + mShortcutInfo.getId().hashCode();
+        }
+        hashCode = 31 * hashCode + mRank;
+        return hashCode;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 91da8ec..e36d878 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -24,6 +24,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * A representation of an app target event.
@@ -116,6 +117,13 @@
     }
 
     @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(mTarget, mLocation);
+        hashCode = 31 * hashCode + mAction;
+        return hashCode;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/prediction/OWNERS b/core/java/android/app/prediction/OWNERS
index fe012da..73168fb 100644
--- a/core/java/android/app/prediction/OWNERS
+++ b/core/java/android/app/prediction/OWNERS
@@ -1,2 +1,4 @@
+pinyaoting@google.com
+hyunyoungs@google.com
 adamcohen@google.com
 sunnygoyal@google.com
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9e316a2..c8cae82 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4353,13 +4353,6 @@
             "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
 
     /**
-     * Activity Action: Shows the contrast setting dialog.
-     * @hide
-     */
-    public static final String ACTION_SHOW_CONTRAST_DIALOG =
-            "com.android.intent.action.SHOW_CONTRAST_DIALOG";
-
-    /**
      * Broadcast Action:  A global button was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
      * caused the broadcast.
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index f08395a..41a4288 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -56,6 +56,10 @@
         }
       ],
       "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
+    },
+    {
+      "name": "CtsWindowManagerBackgroundActivityTestCases",
+      "file_patterns": ["(/|^)IntentSender.java"]
     }
   ],
   "ravenwood-presubmit": [
@@ -65,9 +69,5 @@
     }
   ],
   "postsubmit": [
-    {
-      "name": "CtsWindowManagerBackgroundActivityTestCases",
-      "file_patterns": ["(/|^)IntentSender.java"]
-    }
   ]
 }
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 83742eb..e2a131c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -247,3 +247,13 @@
     description: "Allow MAIN user to access blocked number provider"
     bug: "338579331"
 }
+
+flag {
+    name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles"
+    namespace: "profile_experiences"
+    description: "Use user states to check the state of quiet mode for managed profiles only"
+    bug: "332812630"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index d683d72..1eb466c 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -731,6 +731,7 @@
      * commits, or is rolled back, either explicitly or by a call to
      * {@link #yieldIfContendedSafely}.
      */
+    // TODO(340874899) Provide an Executor overload
     public void beginTransactionWithListener(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, true);
@@ -760,6 +761,7 @@
      *            transaction begins, commits, or is rolled back, either
      *            explicitly or by a call to {@link #yieldIfContendedSafely}.
      */
+    // TODO(340874899) Provide an Executor overload
     public void beginTransactionWithListenerNonExclusive(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, false);
@@ -785,6 +787,8 @@
      *   }
      * </pre>
      */
+    // TODO(340874899) Provide an Executor overload
+    @SuppressLint("ExecutorRegistration")
     @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionWithListenerReadOnly(
             @Nullable SQLiteTransactionListener transactionListener) {
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 000f6ef..b41a607 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 01deb49..b93cd46 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -583,6 +583,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "7211222997110112110": {
+      "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/ActivityRefresher.java"
+    },
     "1665699123574159131": {
       "message": "Starting activity when config will change = %b",
       "level": "VERBOSE",
@@ -1771,12 +1777,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
     },
-    "-7756685416834187936": {
-      "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
-    },
     "-5176775281239247368": {
       "message": "Reverting orientation after camera compat force rotation",
       "level": "VERBOSE",
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 ddf58fb..38c3443 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
@@ -87,6 +87,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -1866,7 +1867,11 @@
 
         @Override
         public void bubbleOverflowChanged(boolean hasBubbles) {
-            // TODO (b/334175587): tell stack view to hide / show the overflow
+            if (Flags.enableOptionalBubbleOverflow()) {
+                if (mStackView != null) {
+                    mStackView.showOverflow(hasBubbles);
+                }
+            }
         }
     };
 
@@ -2719,6 +2724,15 @@
                     () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
                             sensitiveNotificationProtectionActive));
         }
+
+        @Override
+        public boolean canShowBubbleNotification() {
+            // in bubble bar mode, when the IME is visible we can't animate new bubbles.
+            if (BubbleController.this.isShowingAsBubbleBar()) {
+                return !BubbleController.this.mBubblePositioner.getIsImeVisible();
+            }
+            return true;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 633b01b..18e04d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -44,6 +44,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ContrastColorUtil;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 
 import java.util.ArrayList;
@@ -195,7 +196,9 @@
     }
 
     void updateEmptyStateVisibility() {
-        mEmptyState.setVisibility(mOverflowBubbles.isEmpty() ? View.VISIBLE : View.GONE);
+        boolean showEmptyState = mOverflowBubbles.isEmpty()
+                && !Flags.enableOptionalBubbleOverflow();
+        mEmptyState.setVisibility(showEmptyState ? View.VISIBLE : View.GONE);
         mRecyclerView.setVisibility(mOverflowBubbles.isEmpty() ? View.GONE : View.VISIBLE);
     }
 
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 c4bbe32..a35a004 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
@@ -324,6 +324,11 @@
         return 0;
     }
 
+    /** Returns whether the IME is visible. */
+    public boolean getIsImeVisible() {
+        return mImeVisible;
+    }
+
     /** Sets whether the IME is visible. **/
     public void setImeVisible(boolean visible, int height) {
         mImeVisible = visible;
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 be88b34..9fabd42 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
@@ -80,6 +80,7 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
@@ -863,6 +864,7 @@
         }
     };
 
+    private boolean mShowingOverflow;
     private BubbleOverflow mBubbleOverflow;
     private StackEducationView mStackEduView;
     private StackEducationView.Manager mStackEducationViewManager;
@@ -992,18 +994,12 @@
 
         mBubbleOverflow = mBubbleData.getOverflow();
 
-        resetOverflowView();
-        mBubbleContainer.addView(mBubbleOverflow.getIconView(),
-                mBubbleContainer.getChildCount() /* index */,
-                new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
-                        mPositioner.getBubbleSize()));
-        updateOverflow();
-        mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
-            mBubbleData.setShowingOverflow(true);
-            mBubbleData.setSelectedBubble(mBubbleOverflow);
-            mBubbleData.setExpanded(true);
-        });
-
+        if (Flags.enableOptionalBubbleOverflow()) {
+            showOverflow(mBubbleData.hasOverflowBubbles());
+        } else {
+            mShowingOverflow = true; // if the flags not on this is always true
+            setUpOverflow();
+        }
         mScrim = new View(getContext());
         mScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         mScrim.setBackgroundDrawable(new ColorDrawable(
@@ -1220,6 +1216,19 @@
         }
     };
 
+    private void setUpOverflow() {
+        resetOverflowView();
+        mBubbleContainer.addView(mBubbleOverflow.getIconView(),
+                mBubbleContainer.getChildCount() /* index */,
+                new FrameLayout.LayoutParams(mBubbleSize, mBubbleSize));
+        updateOverflow();
+        mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
+            mBubbleData.setShowingOverflow(true);
+            mBubbleData.setSelectedBubble(mBubbleOverflow);
+            mBubbleData.setExpanded(true);
+        });
+    }
+
     private void setUpDismissView() {
         if (mDismissView != null) {
             removeView(mDismissView);
@@ -1458,24 +1467,56 @@
                 b.getExpandedView().updateFontSize();
             }
         }
-        if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+        if (mShowingOverflow && mBubbleOverflow != null
+                && mBubbleOverflow.getExpandedView() != null) {
             mBubbleOverflow.getExpandedView().updateFontSize();
         }
     }
 
     void updateLocale() {
-        if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+        if (mShowingOverflow && mBubbleOverflow != null
+                && mBubbleOverflow.getExpandedView() != null) {
             mBubbleOverflow.getExpandedView().updateLocale();
         }
     }
 
     private void updateOverflow() {
         mBubbleOverflow.update();
-        mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
-                mBubbleContainer.getChildCount() - 1 /* index */);
+        if (mShowingOverflow) {
+            mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
+                    mBubbleContainer.getChildCount() - 1 /* index */);
+        }
         updateOverflowVisibility();
     }
 
+    private void updateOverflowVisibility() {
+        mBubbleOverflow.setVisible(mShowingOverflow
+                && (mIsExpanded || mBubbleData.isShowingOverflow())
+                ? VISIBLE
+                : GONE);
+    }
+
+    private void updateOverflowDotVisibility(boolean expanding) {
+        if (mShowingOverflow && mBubbleOverflow.showDot()) {
+            mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> {
+                mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE);
+            });
+        }
+    }
+
+    /**  Sets whether the overflow should be visible or not. */
+    public void showOverflow(boolean showOverflow) {
+        if (!Flags.enableOptionalBubbleOverflow()) return;
+        if (mShowingOverflow != showOverflow) {
+            mShowingOverflow = showOverflow;
+            if (showOverflow) {
+                setUpOverflow();
+            } else if (mBubbleOverflow != null) {
+                resetOverflowView();
+            }
+        }
+    }
+
     /**
      * Handle theme changes.
      */
@@ -1535,7 +1576,10 @@
                 b.getExpandedView().updateDimensions();
             }
         }
-        mBubbleOverflow.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
+        if (mShowingOverflow) {
+            mBubbleOverflow.getIconView().setLayoutParams(
+                    new LayoutParams(mBubbleSize, mBubbleSize));
+        }
         mExpandedAnimationController.updateResources();
         mStackAnimationController.updateResources();
         mDismissView.updateResources();
@@ -1699,7 +1743,7 @@
                     bubble.getIconView().setContentDescription(getResources().getString(
                             R.string.bubble_content_description_single, titleStr, appName));
                 } else {
-                    final int moreCount = mBubbleContainer.getChildCount() - 1;
+                    final int moreCount = getBubbleCount();
                     bubble.getIconView().setContentDescription(getResources().getString(
                             R.string.bubble_content_description_stack,
                             titleStr, appName, moreCount));
@@ -1752,7 +1796,8 @@
 
             View bubbleOverflowIconView =
                     mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null;
-            if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) {
+            if (mShowingOverflow && bubbleOverflowIconView != null
+                    && !mBubbleData.getBubbles().isEmpty()) {
                 Bubble lastBubble =
                         mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1);
                 View lastBubbleIconView = lastBubble.getIconView();
@@ -1928,20 +1973,6 @@
         }
     }
 
-    private void updateOverflowVisibility() {
-        mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow())
-                ? VISIBLE
-                : GONE);
-    }
-
-    private void updateOverflowDotVisibility(boolean expanding) {
-        if (mBubbleOverflow.showDot()) {
-            mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> {
-                mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE);
-            });
-        }
-    }
-
     // via BubbleData.Listener
     void updateBubble(Bubble bubble) {
         animateInFlyoutForBubble(bubble);
@@ -3428,8 +3459,9 @@
      * @return the number of bubbles in the stack view.
      */
     public int getBubbleCount() {
-        // Subtract 1 for the overflow button that is always in the bubble container.
-        return mBubbleContainer.getChildCount() - 1;
+        final int childCount = mBubbleContainer.getChildCount();
+        // Subtract 1 for the overflow button if it's showing.
+        return mShowingOverflow ? childCount - 1 : childCount;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 322088b..1d053f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -297,6 +297,15 @@
             boolean sensitiveNotificationProtectionActive);
 
     /**
+     * Determines whether Bubbles can show notifications.
+     *
+     * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble
+     * notification is suppressed and should be shown by the Notifications pipeline as regular
+     * notifications.
+     */
+    boolean canShowBubbleNotification();
+
+    /**
      * A listener to be notified of bubble state changes, used by launcher to render bubbles in
      * its process.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 414a9d1..01364d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -133,6 +133,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
             @NonNull PipTransitionState pipTransitionState,
+            @NonNull PipScheduler pipScheduler,
             @NonNull SizeSpecSource sizeSpecSource,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -140,7 +141,7 @@
             @ShellMainThread ShellExecutor mainExecutor,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
         return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper,
+                pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
                 floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                 pipPerfHintControllerOptional);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index a097a0f..be10151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -58,7 +58,8 @@
  * A helper to animate and manipulate the PiP.
  */
 public class PipMotionHelper implements PipAppOpsListener.Callback,
-        FloatingContentCoordinator.FloatingContent {
+        FloatingContentCoordinator.FloatingContent,
+        PipTransitionState.PipTransitionStateChangedListener {
     private static final String TAG = "PipMotionHelper";
     private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
     private static final boolean DEBUG = false;
@@ -181,7 +182,7 @@
             }
         };
         mPipTransitionState = pipTransitionState;
-        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
     }
 
     void init() {
@@ -687,7 +688,8 @@
         // setAnimatingToBounds(toBounds);
     }
 
-    private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
             @PipTransitionState.TransitionState int newState,
             @Nullable Bundle extra) {
         switch (newState) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 04cf350..b55a41d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -24,6 +24,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.os.Bundle;
 import android.os.Looper;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -32,6 +33,7 @@
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.VisibleForTesting;
@@ -51,16 +53,20 @@
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
  * trigger dynamic resize.
  */
-public class PipResizeGestureHandler {
+public class PipResizeGestureHandler implements
+        PipTransitionState.PipTransitionStateChangedListener {
 
     private static final String TAG = "PipResizeGestureHandler";
     private static final int PINCH_RESIZE_SNAP_DURATION = 250;
     private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
+    private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change";
 
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final PipBoundsState mPipBoundsState;
     private final PipTouchState mPipTouchState;
+    private final PipScheduler mPipScheduler;
+    private final PipTransitionState mPipTransitionState;
     private final PhonePipMenuController mPhonePipMenuController;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
@@ -88,6 +94,7 @@
     private boolean mIsSysUiStateValid;
     private boolean mThresholdCrossed;
     private boolean mOngoingPinchToResize = false;
+    private boolean mWaitingForBoundsChangeTransition = false;
     private float mAngle = 0;
     int mFirstIndex = -1;
     int mSecondIndex = -1;
@@ -104,11 +111,17 @@
     private int mCtrlType;
     private int mOhmOffset;
 
-    public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipTouchState pipTouchState,
+    public PipResizeGestureHandler(Context context,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState,
+            PipTouchState pipTouchState,
+            PipScheduler pipScheduler,
+            PipTransitionState pipTransitionState,
             Runnable updateMovementBoundsRunnable,
-            PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
-            ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
+            PipUiEventLogger pipUiEventLogger,
+            PhonePipMenuController menuActivityController,
+            ShellExecutor mainExecutor,
+            @Nullable PipPerfHintController pipPerfHintController) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = mainExecutor;
@@ -116,6 +129,11 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mPipTouchState = pipTouchState;
+        mPipScheduler = pipScheduler;
+
+        mPipTransitionState = pipTransitionState;
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
+
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -125,6 +143,7 @@
             mUserResizeBounds.set(rect);
             // mMotionHelper.synchronizePinnedStackBounds();
             mUpdateMovementBoundsRunnable.run();
+            mPipBoundsState.setBounds(rect);
             resetState();
         };
     }
@@ -202,7 +221,7 @@
     @VisibleForTesting
     void onInputEvent(InputEvent ev) {
         if (!mEnablePinchResize) {
-            // No need to handle anything if neither form of resizing is enabled.
+            // No need to handle anything if resizing isn't enabled.
             return;
         }
 
@@ -227,7 +246,7 @@
                 }
             }
 
-            if (mEnablePinchResize && mOngoingPinchToResize) {
+            if (mOngoingPinchToResize) {
                 onPinchResize(mv);
             }
         }
@@ -249,13 +268,11 @@
     }
 
     boolean willStartResizeGesture(MotionEvent ev) {
-        if (isInValidSysUiState()) {
-            if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                if (mEnablePinchResize && ev.getPointerCount() == 2) {
-                    onPinchResize(ev);
-                    mOngoingPinchToResize = mAllowGesture;
-                    return mAllowGesture;
-                }
+        if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+            if (mEnablePinchResize && ev.getPointerCount() == 2) {
+                onPinchResize(ev);
+                mOngoingPinchToResize = mAllowGesture;
+                return mAllowGesture;
             }
         }
         return false;
@@ -284,7 +301,6 @@
             mSecondIndex = -1;
             mAllowGesture = false;
             finishResize();
-            cleanUpHighPerfSessionMaybe();
         }
 
         if (ev.getPointerCount() != 2) {
@@ -347,10 +363,7 @@
                         mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
                         mDownBounds, mLastResizeBounds);
 
-                /*
-                mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
-                        mAngle, null);
-                 */
+                mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
                 mPipBoundsState.setHasUserResizedPip(true);
             }
         }
@@ -399,57 +412,43 @@
     }
 
     private void finishResize() {
-        if (!mLastResizeBounds.isEmpty()) {
-            // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
-            // position correctly. Drag-resize does not need to move, so just finalize resize.
-            if (mOngoingPinchToResize) {
-                final Rect startBounds = new Rect(mLastResizeBounds);
-                // If user resize is pretty close to max size, just auto resize to max.
-                if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
-                        || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
-                    resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
-                }
-
-                // If user resize is smaller than min size, auto resize to min
-                if (mLastResizeBounds.width() < mMinSize.x
-                        || mLastResizeBounds.height() < mMinSize.y) {
-                    resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
-                }
-
-                // get the current movement bounds
-                final Rect movementBounds = mPipBoundsAlgorithm
-                        .getMovementBounds(mLastResizeBounds);
-
-                // snap mLastResizeBounds to the correct edge based on movement bounds
-                snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
-
-                final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
-                        mLastResizeBounds, movementBounds);
-                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
-
-                // disable any touch events beyond resizing too
-                mPipTouchState.setAllowInputEvents(false);
-
-                /*
-                mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
-                        PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
-                            // enable touch events
-                            mPipTouchState.setAllowInputEvents(true);
-                        });
-                 */
-            } else {
-                /*
-                mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
-                        TRANSITION_DIRECTION_USER_RESIZE,
-                        mUpdateResizeBoundsCallback);
-                 */
-            }
-            final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
-            mPipUiEventLogger.log(
-                    PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
-        } else {
+        if (mLastResizeBounds.isEmpty()) {
             resetState();
         }
+        if (!mOngoingPinchToResize) {
+            return;
+        }
+        final Rect startBounds = new Rect(mLastResizeBounds);
+
+        // If user resize is pretty close to max size, just auto resize to max.
+        if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+                || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+            resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+        }
+
+        // If user resize is smaller than min size, auto resize to min
+        if (mLastResizeBounds.width() < mMinSize.x
+                || mLastResizeBounds.height() < mMinSize.y) {
+            resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+        }
+
+        // get the current movement bounds
+        final Rect movementBounds = mPipBoundsAlgorithm
+                .getMovementBounds(mLastResizeBounds);
+
+        // snap mLastResizeBounds to the correct edge based on movement bounds
+        snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
+        final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+                mLastResizeBounds, movementBounds);
+        mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+        // Update the transition state to schedule a resize transition.
+        Bundle extra = new Bundle();
+        extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+
+        mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
     }
 
     private void resetState() {
@@ -509,6 +508,40 @@
         rect.set(l, t, r, b);
     }
 
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+        switch (newState) {
+            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+                if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
+                mWaitingForBoundsChangeTransition = true;
+                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
+                break;
+            case PipTransitionState.CHANGING_PIP_BOUNDS:
+                if (!mWaitingForBoundsChangeTransition) break;
+
+                // If bounds change transition was scheduled from this class, handle leash updates.
+                mWaitingForBoundsChangeTransition = false;
+
+                SurfaceControl.Transaction startTx = extra.getParcelable(
+                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+                Rect destinationBounds = extra.getParcelable(
+                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+                        destinationBounds.left, destinationBounds.top);
+                startTx.apply();
+
+                // All motion operations have actually finished, so make bounds cache updates.
+                cleanUpHighPerfSessionMaybe();
+
+                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
+                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+
+                mUpdateResizeBoundsCallback.accept(destinationBounds);
+                break;
+        }
+    }
+
     /**
      * Dumps the {@link PipResizeGestureHandler} state.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index c5b0de3..4947507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
@@ -165,6 +166,16 @@
      * {@link WindowContainerTransaction}.
      */
     public void scheduleUserResizePip(Rect toBounds) {
+        scheduleUserResizePip(toBounds, 0f /* degrees */);
+    }
+
+    /**
+     * Directly perform a scaled matrix transformation on the leash. This will not perform any
+     * {@link WindowContainerTransaction}.
+     *
+     * @param degrees the angle to rotate the bounds to.
+     */
+    public void scheduleUserResizePip(Rect toBounds, float degrees) {
         if (toBounds.isEmpty()) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
@@ -172,7 +183,16 @@
         }
         SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        tx.setPosition(leash, toBounds.left, toBounds.top);
+
+        Matrix transformTensor = new Matrix();
+        final float[] mMatrixTmp = new float[9];
+        final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width();
+
+        transformTensor.setScale(scale, scale);
+        transformTensor.postTranslate(toBounds.left, toBounds.top);
+        transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());
+
+        tx.setMatrix(leash, transformTensor, mMatrixTmp);
         tx.apply();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 9c6e3ea..319d199 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -73,7 +73,7 @@
  * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
  * the PIP.
  */
-public class PipTouchHandler {
+public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener {
 
     private static final String TAG = "PipTouchHandler";
     private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
@@ -84,6 +84,7 @@
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     @NonNull private final PipBoundsState mPipBoundsState;
     @NonNull private final PipTransitionState mPipTransitionState;
+    @NonNull private final PipScheduler mPipScheduler;
     @NonNull private final SizeSpecSource mSizeSpecSource;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -173,6 +174,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
             @NonNull PipTransitionState pipTransitionState,
+            @NonNull PipScheduler pipScheduler,
             @NonNull SizeSpecSource sizeSpecSource,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -188,6 +190,7 @@
 
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+        mPipScheduler = pipScheduler;
         mSizeSpecSource = sizeSpecSource;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -213,10 +216,10 @@
                 },
                 menuController::hideMenu,
                 mainExecutor);
-        mPipResizeGestureHandler =
-                new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
-                        mTouchState, this::updateMovementBounds, pipUiEventLogger,
-                        menuController, mainExecutor, mPipPerfHintController);
+        mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
+                pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
+                this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+                mPipPerfHintController);
         mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
 
         if (PipUtils.isPip2ExperimentEnabled()) {
@@ -1075,7 +1078,8 @@
         mPipResizeGestureHandler.setOhmOffset(offset);
     }
 
-    private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
             @PipTransitionState.TransitionState int newState,
             @Nullable Bundle extra) {
         switch (newState) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4d3c763..6224543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,7 +31,6 @@
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -567,15 +566,15 @@
         final int mode = change.getMode();
         // Put all the OPEN/SHOW on top
         if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
-            if (isOpening
-                    // This is for when an activity launches while a different transition is
-                    // collecting.
-                    || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+            if (isOpening) {
                 // put on top
                 return zSplitLine + numChanges - i;
-            } else {
+            } else if (isClosing) {
                 // put on bottom
                 return zSplitLine - i;
+            } else {
+                // maintain relative ordering (put all changes in the animating layer)
+                return zSplitLine + numChanges - i;
             }
         } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
             if (isOpening) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index b43ff63..a488756 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -252,18 +252,14 @@
         return 0;
     }
 
-    /**
-     * Get the current playback info for this session.
-     *
-     * @return The current playback info or null.
-     */
-    public @Nullable PlaybackInfo getPlaybackInfo() {
+    /** Returns the current playback info for this session. */
+    @NonNull
+    public PlaybackInfo getPlaybackInfo() {
         try {
             return mSessionBinder.getVolumeAttributes();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getAudioInfo.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index f674b06a..c3c74a6 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.app.Service;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -404,6 +405,7 @@
      *
      * @param frame A description of the polling frame.
      */
+    @SuppressLint("OnNameExpected")
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void processPollingFrames(@NonNull List<PollingFrame> frame) {
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 8276e18..6e9bde4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -29,46 +29,36 @@
 import kotlinx.coroutines.flow.map
 
 interface IAppOpsController {
-    val modeFlow: Flow<Int>
+    val mode: Flow<Int>
     val isAllowed: Flow<Boolean>
-        get() = modeFlow.map { it == MODE_ALLOWED }
+        get() = mode.map { it == MODE_ALLOWED }
 
     fun setAllowed(allowed: Boolean)
 
     @Mode fun getMode(): Int
 }
 
-data class AppOps(
-    val op: Int,
-    val modeForNotAllowed: Int = MODE_ERRORED,
-
-    /**
-     * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
-     *
-     * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
-     */
-    val setModeByUid: Boolean = false,
-)
-
 class AppOpsController(
     context: Context,
     private val app: ApplicationInfo,
-    private val appOps: AppOps,
+    private val op: Int,
+    private val modeForNotAllowed: Int = MODE_ERRORED,
+    private val setModeByUid: Boolean = false,
 ) : IAppOpsController {
     private val appOpsManager = context.appOpsManager
     private val packageManager = context.packageManager
-    override val modeFlow = appOpsManager.opModeFlow(appOps.op, app)
+    override val mode = appOpsManager.opModeFlow(op, app)
 
     override fun setAllowed(allowed: Boolean) {
-        val mode = if (allowed) MODE_ALLOWED else appOps.modeForNotAllowed
+        val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
 
-        if (appOps.setModeByUid) {
-            appOpsManager.setUidMode(appOps.op, app.uid, mode)
+        if (setModeByUid) {
+            appOpsManager.setUidMode(op, app.uid, mode)
         } else {
-            appOpsManager.setMode(appOps.op, app.uid, app.packageName, mode)
+            appOpsManager.setMode(op, app.uid, app.packageName, mode)
         }
 
-        val permission = AppOpsManager.opToPermission(appOps.op)
+        val permission = AppOpsManager.opToPermission(op)
         if (permission != null) {
             packageManager.updatePermissionFlags(permission, app.packageName,
                     PackageManager.FLAG_PERMISSION_USER_SET,
@@ -77,6 +67,5 @@
         }
     }
 
-    @Mode
-    override fun getMode(): Int = appOpsManager.getOpMode(appOps.op, app)
+    @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 37b1d73..5db5eae 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spa.framework.util.filterItem
-import com.android.settingslib.spaprivileged.model.app.AppOps
 import com.android.settingslib.spaprivileged.model.app.AppOpsController
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
@@ -45,11 +44,11 @@
     private val packageManagers: IPackageManagers = PackageManagers,
 ) : TogglePermissionAppListModel<AppOpPermissionRecord> {
 
-    abstract val appOps: AppOps
+    abstract val appOp: Int
     abstract val permission: String
 
     override val enhancedConfirmationKey: String?
-        get() = AppOpsManager.opToPublicName(appOps.op)
+        get() = AppOpsManager.opToPublicName(appOp)
 
     /**
      * When set, specifies the broader permission who trumps the [permission].
@@ -66,12 +65,27 @@
      */
     open val permissionHasAppOpFlag: Boolean = true
 
+    open val modeForNotAllowed: Int = AppOpsManager.MODE_ERRORED
+
+    /**
+     * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
+     *
+     * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
+     */
+    open val setModeByUid = false
+
     /** These not changeable packages will also be hidden from app list. */
     private val notChangeablePackages =
         setOf("android", "com.android.systemui", context.packageName)
 
     private fun createAppOpsController(app: ApplicationInfo) =
-        AppOpsController(context, app, appOps)
+        AppOpsController(
+            context = context,
+            app = app,
+            op = appOp,
+            setModeByUid = setModeByUid,
+            modeForNotAllowed = modeForNotAllowed,
+        )
 
     private fun createRecord(
         app: ApplicationInfo,
@@ -152,7 +166,7 @@
         return { true }
     }
 
-    val mode = appOpsController.modeFlow.collectAsStateWithLifecycle(initialValue = null)
+    val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null)
     return {
         when (mode.value) {
             null -> null
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 74a7c14..91bbd9f 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -27,14 +27,16 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doNothing
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
@@ -42,18 +44,28 @@
 class AppOpsControllerTest {
     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
 
-    private val appOpsManager = mock<AppOpsManager>()
+    @Spy private val context: Context = ApplicationProvider.getApplicationContext()
 
-    private val packageManager = mock<PackageManager>()
+    @Mock private lateinit var appOpsManager: AppOpsManager
 
-    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
-        on { appOpsManager } doReturn appOpsManager
-        on { packageManager } doReturn packageManager
+    @Mock private lateinit var packageManager: PackageManager
+
+    @Before
+    fun setUp() {
+        whenever(context.appOpsManager).thenReturn(appOpsManager)
+        whenever(context.packageManager).thenReturn(packageManager)
+        doNothing().whenever(packageManager)
+                .updatePermissionFlags(any(), any(), any(), any(), any())
     }
 
     @Test
     fun setAllowed_setToTrue() {
-        val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP))
+        val controller =
+            AppOpsController(
+                context = context,
+                app = APP,
+                op = OP,
+            )
 
         controller.setAllowed(true)
 
@@ -62,7 +74,12 @@
 
     @Test
     fun setAllowed_setToFalse() {
-        val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP))
+        val controller =
+            AppOpsController(
+                context = context,
+                app = APP,
+                op = OP,
+            )
 
         controller.setAllowed(false)
 
@@ -71,11 +88,13 @@
 
     @Test
     fun setAllowed_setToFalseWithModeForNotAllowed() {
-        val controller = AppOpsController(
-            context = context,
-            app = APP,
-            appOps = AppOps(op = OP, modeForNotAllowed = MODE_IGNORED),
-        )
+        val controller =
+            AppOpsController(
+                context = context,
+                app = APP,
+                op = OP,
+                modeForNotAllowed = MODE_IGNORED,
+            )
 
         controller.setAllowed(false)
 
@@ -84,11 +103,13 @@
 
     @Test
     fun setAllowed_setToTrueByUid() {
-        val controller = AppOpsController(
-            context = context,
-            app = APP,
-            appOps = AppOps(op = OP, setModeByUid = true),
-        )
+        val controller =
+            AppOpsController(
+                context = context,
+                app = APP,
+                op = OP,
+                setModeByUid = true,
+            )
 
         controller.setAllowed(true)
 
@@ -97,11 +118,13 @@
 
     @Test
     fun setAllowed_setToFalseByUid() {
-        val controller = AppOpsController(
-            context = context,
-            app = APP,
-            appOps = AppOps(op = OP, setModeByUid = true),
-        )
+        val controller =
+            AppOpsController(
+                context = context,
+                app = APP,
+                op = OP,
+                setModeByUid = true,
+            )
 
         controller.setAllowed(false)
 
@@ -112,7 +135,12 @@
     fun getMode() {
         whenever(appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName))
             .thenReturn(MODE_ALLOWED)
-        val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP))
+        val controller =
+            AppOpsController(
+                context = context,
+                app = APP,
+                op = OP,
+            )
 
         val mode = controller.getMode()
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index 07ccdd5..bb25cf3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
-import com.android.settingslib.spaprivileged.model.app.AppOps
 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
@@ -40,6 +39,7 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
@@ -287,6 +287,16 @@
         assertThat(appOpsController.setAllowedCalledWith).isTrue()
     }
 
+    @Test
+    fun setAllowed_setModeByUid() {
+        listModel.setModeByUid = true
+        val record = listModel.transformItem(APP)
+
+        listModel.setAllowed(record = record, newAllowed = true)
+
+        verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED)
+    }
+
     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
         lateinit var isAllowedState: () -> Boolean?
         composeTestRule.setContent { isAllowedState = listModel.isAllowed(record) }
@@ -299,9 +309,11 @@
         override val switchTitleResId = R.string.test_app_op_permission_switch_title
         override val footerResId = R.string.test_app_op_permission_footer
 
-        override val appOps = AppOps(AppOpsManager.OP_MANAGE_MEDIA)
+        override val appOp = AppOpsManager.OP_MANAGE_MEDIA
         override val permission = PERMISSION
         override var broaderPermission: String? = null
+
+        override var setModeByUid = false
     }
 
     private companion object {
@@ -317,7 +329,7 @@
 private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
     var setAllowedCalledWith: Boolean? = null
 
-    override val modeFlow = flowOf(fakeMode)
+    override val mode = flowOf(fakeMode)
 
     override fun setAllowed(allowed: Boolean) {
         setAllowedCalledWith = allowed
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b9e70ef..9c58371 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -826,20 +826,6 @@
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".contrast.ContrastDialogActivity"
-            android:label="@string/quick_settings_contrast_label"
-            android:theme="@style/Theme.SystemUI.ContrastDialog"
-            android:finishOnCloseSystemDialogs="true"
-            android:launchMode="singleInstance"
-            android:excludeFromRecents="true"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".ForegroundServicesDialog"
             android:process=":fgservices"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 755fe2a..55edff6 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -4,6 +4,13 @@
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
+    name: "create_windowless_window_magnifier"
+    namespace: "accessibility"
+    description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
+    bug: "280992417"
+}
+
+flag {
     name: "delay_show_magnification_button"
     namespace: "accessibility"
     description: "Delays the showing of magnification mode switch button."
@@ -66,8 +73,11 @@
 }
 
 flag {
-    name: "create_windowless_window_magnifier"
+    name: "save_and_restore_magnification_settings_buttons"
     namespace: "accessibility"
-    description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
-    bug: "280992417"
+    description: "Saves the selected button status in magnification settings and restore the status when revisiting the same smallest screen DP."
+    bug: "325567876"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0ef12e2..f9e955b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,14 +26,6 @@
 }
 
 flag {
-
-    name: "notification_heads_up_cycling"
-    namespace: "systemui"
-    description: "Heads-up notification cycling animation for the Notification Avalanche feature."
-    bug: "316404716"
-}
-
-flag {
    name: "priority_people_section"
    namespace: "systemui"
    description: "Add a new section for priority people (aka important conversations)."
@@ -204,7 +196,16 @@
     description: "Re-enable the codepath that removed tinting of notifications when the"
         " standard background color is desired.  This was the behavior before we discovered"
         " a resources threading issue, which we worked around by tinting the notification"
-        " backgrounds and footer buttons."
+        " backgrounds."
+    bug: "294830092"
+}
+
+flag {
+    name: "notification_footer_background_tint_optimization"
+    namespace: "systemui"
+    description: "Remove duplicative tinting of notification footer buttons. This was the behavior"
+        " before we discovered a resources threading issue, which we worked around by applying the"
+        " same color as a tint to the background drawable of footer buttons."
     bug: "294830092"
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 6d8c47d..ca4ff83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.platform.LocalView
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -60,6 +61,6 @@
         }
 
         val blueprint = blueprintByBlueprintId[blueprintId] ?: return
-        with(blueprint) { Content(modifier) }
+        with(blueprint) { Content(modifier.sysuiResTag("keyguard_root_view")) }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index abff93d..a39fa64 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -31,6 +31,7 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -129,7 +130,7 @@
                     with(lockSection) { LockIcon() }
 
                     // Aligned to bottom and constrained to below the lock icon.
-                    Column(modifier = Modifier.fillMaxWidth()) {
+                    Column(modifier = Modifier.fillMaxWidth().sysuiResTag("keyguard_bottom_area")) {
                         if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 88b8298..0673153 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -36,7 +36,6 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
@@ -80,7 +79,7 @@
             }
 
         SceneTransitionLayout(
-            modifier = modifier.sysuiResTag("keyguard_clock_container"),
+            modifier = modifier,
             currentScene = currentScene,
             onChangeScene = {},
             transitions = ClockTransition.defaultClockTransitions,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
index 312c14d..fec56ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -18,9 +18,13 @@
 
 import android.content.applicationContext
 import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
 import androidx.test.filters.SmallTest
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testDispatcher
@@ -40,15 +44,19 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     private val kosmos = testKosmos()
 
-    private val fakeUserRepository = kosmos.fakeUserRepository
-
     private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
         whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
         whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
@@ -130,7 +138,83 @@
             }
         }
 
-    private companion object {
-        val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+    @Test
+    @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+    fun brightnessBaseUserRestriction_flagOff_noRestriction() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+            }
+        }
+
+    @Test
+    fun bothRestrictions_returnsSetEnforcedAdminFromCheck() =
+        with(kosmos) {
+            testScope.runTest {
+                val enforcedAdmin: EnforcedAdmin =
+                    EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+                whenever(
+                        mockUserRestrictionChecker.checkIfRestrictionEnforced(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(enforcedAdmin)
+
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+            }
+        }
+
+    @Test
+    @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+    fun brightnessBaseUserRestriction_flagOn_emptyRestriction() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
+            }
+        }
+
+    companion object {
+        private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
index 85a4bcf..11f5238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
@@ -48,7 +48,6 @@
     private val kosmos = testKosmos()
 
     private val mockActivityStarter = kosmos.activityStarter
-    private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
 
     private val underTest =
         with(kosmos) {
@@ -70,7 +69,18 @@
 
                 fakeBrightnessPolicyRepository.setCurrentUserRestricted()
 
-                assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+                assertThat(restriction)
+                    .isEqualTo(
+                        PolicyRestriction.Restricted(
+                            EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                                BrightnessPolicyRepository.RESTRICTION
+                            )
+                        )
+                    )
+
+                fakeBrightnessPolicyRepository.setBaseUserRestriction()
+
+                assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 766798c..83227e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -204,14 +204,14 @@
         }
 
     @Test
-    fun isCommunalAvailable_whenDreaming_true() =
+    fun isCommunalAvailable_whenKeyguardShowing_true() =
         testScope.runTest {
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setDreaming(true)
+            keyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isTrue()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
new file mode 100644
index 0000000..1e7ed63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FingerprintPropertyRepositoryTest : SysuiTestCase() {
+    @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+    private val testScope = TestScope()
+    private lateinit var underTest: FingerprintPropertyRepositoryImpl
+    @Mock private lateinit var fingerprintManager: FingerprintManager
+    @Captor
+    private lateinit var fingerprintCallbackCaptor:
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
+
+    @Before
+    fun setup() {
+        underTest =
+            FingerprintPropertyRepositoryImpl(
+                testScope.backgroundScope,
+                Dispatchers.Main.immediate,
+                fingerprintManager,
+            )
+    }
+
+    @Test
+    fun propertiesInitialized_onStartFalse() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+        }
+
+    @Test
+    fun propertiesInitialized_onStartTrue() =
+        testScope.runTest {
+            //            // collect sensorType to update fingerprintCallback before
+            // propertiesInitialized
+            //            // is listened for
+            val sensorType by collectLastValue(underTest.sensorType)
+            runCurrent()
+            captureFingerprintCallback()
+
+            fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    @Test
+    fun propertiesInitialized_updatedToTrue() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+
+            captureFingerprintCallback()
+            fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    private fun captureFingerprintCallback() {
+        verify(fingerprintManager)
+            .addAuthenticatorsRegisteredCallback(fingerprintCallbackCaptor.capture())
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index 4226a9d..0551bfb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -109,7 +109,7 @@
             assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
             assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
 
-            underTest.onAttached()
+            underTest.onReorderingAllowed()
 
             mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
             mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bf48784..02a8141 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -69,6 +69,15 @@
     }
 
     @Test
+    fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() {
+        val intent = Intent("test.ACTION")
+
+        underTest.handle(null, intent, true)
+
+        verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true))
+    }
+
+    @Test
     fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
         val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
new file mode 100644
index 0000000..c41ce2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileDataInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.of(1)!!
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val testIntent = mock<Intent>()
+    private val qrCodeScannerController =
+        mock<QRCodeScannerController> {
+            whenever(intent).thenReturn(testIntent)
+            whenever(isAbleToLaunchScannerActivity).thenReturn(false)
+        }
+    private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent)
+    private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+    private val underTest: QRCodeScannerTileDataInteractor =
+        QRCodeScannerTileDataInteractor(
+            testDispatcher,
+            scope.backgroundScope,
+            qrCodeScannerController,
+        )
+
+    @Test
+    fun availability_matchesController_cameraNotAvailable() =
+        scope.runTest {
+            val expectedAvailability = false
+            whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false)
+
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isEqualTo(expectedAvailability)
+        }
+
+    @Test
+    fun availability_matchesController_cameraIsAvailable() =
+        scope.runTest {
+            val expectedAvailability = true
+            whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true)
+
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isEqualTo(expectedAvailability)
+        }
+
+    @Test
+    fun data_matchesController() =
+        scope.runTest {
+            val captor = argumentCaptor<Callback>()
+            val lastData by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+
+            verify(qrCodeScannerController).addCallback(captor.capture())
+            val callback = captor.value
+
+            assertThat(lastData!!).isEqualTo(testUnavailableModel)
+
+            whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true)
+            callback.onQRCodeScannerActivityChanged()
+            runCurrent()
+            assertThat(lastData!!).isEqualTo(testAvailableModel)
+
+            whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false)
+            callback.onQRCodeScannerActivityChanged()
+            runCurrent()
+            assertThat(lastData!!).isEqualTo(testUnavailableModel)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..312f180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() {
+    val kosmos = Kosmos()
+    private val inputHandler = kosmos.qsTileIntentUserInputHandler
+    private val underTest = kosmos.qrCodeScannerTileUserActionInteractor
+    private val intent = mock<Intent>()
+
+    @Test
+    fun handleClick_available() = runTest {
+        val inputModel = QRCodeScannerTileModel.Available(intent)
+
+        underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            intent
+        }
+    }
+
+    @Test
+    fun handleClick_temporarilyUnavailable() = runTest {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+
+    @Test
+    fun handleLongClick_available() = runTest {
+        val inputModel = QRCodeScannerTileModel.Available(intent)
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+
+    @Test
+    fun handleLongClick_temporarilyUnavailable() = runTest {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
new file mode 100644
index 0000000..d26a213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.ui
+
+import android.content.Intent
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsQRCodeScannerTileConfig
+
+    private lateinit var mapper: QRCodeScannerTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            QRCodeScannerTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(
+                            com.android.systemui.res.R.drawable.ic_qr_code_scanner,
+                            TestStubDrawable()
+                        )
+                    }
+                    .resources,
+                context.theme
+            )
+    }
+
+    @Test
+    fun availableModel() {
+        val mockIntent = mock<Intent>()
+        val inputModel = QRCodeScannerTileModel.Available(mockIntent)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createQRCodeScannerTileState(
+                QSTileState.ActivationState.INACTIVE,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun temporarilyUnavailableModel() {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createQRCodeScannerTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                context.getString(
+                    com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createQRCodeScannerTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String?,
+    ): QSTileState {
+        val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
+        return QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+                    null
+                )
+            },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
deleted file mode 100644
index 4181220..0000000
--- a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright 2023, The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<selector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
-    <item android:state_selected="true">
-        <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
-            <stroke
-                android:color="?androidprv:attr/colorAccentPrimary"
-                android:width="@dimen/contrast_dialog_button_stroke_width" />
-            <corners android:radius="@dimen/contrast_dialog_button_radius"/>
-        </shape>
-    </item>
-
-    <item>
-        <layer-list>
-            <item android:top="@dimen/contrast_dialog_button_stroke_width"
-                android:bottom="@dimen/contrast_dialog_button_stroke_width"
-                android:left="@dimen/contrast_dialog_button_stroke_width"
-                android:right="@dimen/contrast_dialog_button_stroke_width">
-                <shape android:shape="rectangle">
-                    <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
-                    <corners android:radius="@dimen/contrast_dialog_button_radius"/>
-                </shape>
-            </item>
-        </layer-list>
-    </item>
-</selector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml
deleted file mode 100644
index aa5b5ab..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_high.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#F2F1E8"
-        android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z"
-        android:strokeColor="#1B1C17" android:strokeWidth="1"/>
-    <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml
deleted file mode 100644
index 89519b8..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_medium.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
-    <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml
deleted file mode 100644
index f914975..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_standard.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
-    <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml
deleted file mode 100644
index 8e885cf..0000000
--- a/packages/SystemUI/res/layout/contrast_dialog.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2023 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal">
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_standard"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_standard"/>
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_standard"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
-        android:layout_height="match_parent" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_medium"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_medium"/>
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_medium"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
-        android:layout_height="match_parent" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_high"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_high"/>
-
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_high"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9cd7d16..9d0319c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1963,15 +1963,6 @@
     <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
     <dimen name="broadcast_dialog_margin">16dp</dimen>
 
-    <!-- Contrast dialog -->
-    <dimen name="contrast_dialog_button_total_size">90dp</dimen>
-    <dimen name="contrast_dialog_button_inner_size">82dp</dimen>
-    <dimen name="contrast_dialog_button_radius">20dp</dimen>
-    <dimen name="contrast_dialog_button_stroke_width">4dp</dimen>
-    <dimen name="contrast_dialog_button_text_size">14sp</dimen>
-    <dimen name="contrast_dialog_button_text_spacing">4dp</dimen>
-    <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen>
-
     <!-- Shadow for dream overlay clock complication -->
     <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
     <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index aecc906..8da8316f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -902,15 +902,6 @@
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
 
-    <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_label">Contrast</string>
-    <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_standard">Standard</string>
-    <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_medium">Medium</string>
-    <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_high">High</string>
-
     <!-- Hearing devices -->
     <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] -->
     <string name="quick_settings_hearing_devices_label">Hearing devices</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2c4cdb9..393a1aa 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -523,10 +523,6 @@
         <item name="android:windowExitAnimation">@anim/instant_fade_out</item>
     </style>
 
-    <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
-        <item name="android:windowBackground">@android:color/transparent</item>
-    </style>
-
     <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
     </style>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
deleted file mode 100644
index 0b0df83..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.recents.model;
-
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.graphics.Bitmap.Config.ARGB_8888;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.util.Log;
-import android.view.WindowInsetsController.Appearance;
-import android.window.TaskSnapshot;
-
-import java.util.HashMap;
-
-/**
- * Data for a single thumbnail.
- */
-public class ThumbnailData {
-
-    public final Bitmap thumbnail;
-    public int orientation;
-    public int rotation;
-    public Rect insets;
-    public Rect letterboxInsets;
-    public boolean reducedResolution;
-    public boolean isRealSnapshot;
-    public boolean isTranslucent;
-    public int windowingMode;
-    public @Appearance int appearance;
-    public float scale;
-    public long snapshotId;
-
-    public ThumbnailData() {
-        thumbnail = null;
-        orientation = ORIENTATION_UNDEFINED;
-        rotation = ROTATION_UNDEFINED;
-        insets = new Rect();
-        letterboxInsets = new Rect();
-        reducedResolution = false;
-        scale = 1f;
-        isRealSnapshot = true;
-        isTranslucent = false;
-        windowingMode = WINDOWING_MODE_UNDEFINED;
-        snapshotId = 0;
-    }
-
-    public void recycleBitmap() {
-        if (thumbnail != null) {
-            thumbnail.recycle();
-        }
-    }
-
-    private static Bitmap makeThumbnail(TaskSnapshot snapshot) {
-        Bitmap thumbnail = null;
-        try (final HardwareBuffer buffer = snapshot.getHardwareBuffer()) {
-            if (buffer != null) {
-                thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace());
-            }
-        } catch (IllegalArgumentException ex) {
-            // TODO(b/157562905): Workaround for a crash when we get a snapshot without this state
-            Log.e("ThumbnailData", "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: "
-                    + snapshot.getHardwareBuffer(), ex);
-        }
-        if (thumbnail == null) {
-            Point taskSize = snapshot.getTaskSize();
-            thumbnail = Bitmap.createBitmap(taskSize.x, taskSize.y, ARGB_8888);
-            thumbnail.eraseColor(Color.BLACK);
-        }
-        return thumbnail;
-    }
-
-    public static HashMap<Integer, ThumbnailData> wrap(int[] taskIds, TaskSnapshot[] snapshots) {
-        HashMap<Integer, ThumbnailData> temp = new HashMap<>();
-        if (taskIds == null || snapshots == null || taskIds.length != snapshots.length) {
-            return temp;
-        }
-
-        for (int i = snapshots.length - 1; i >= 0; i--) {
-            temp.put(taskIds[i], new ThumbnailData(snapshots[i]));
-        }
-        return temp;
-    }
-
-    public ThumbnailData(TaskSnapshot snapshot) {
-        thumbnail = makeThumbnail(snapshot);
-        insets = new Rect(snapshot.getContentInsets());
-        letterboxInsets = new Rect(snapshot.getLetterboxInsets());
-        orientation = snapshot.getOrientation();
-        rotation = snapshot.getRotation();
-        reducedResolution = snapshot.isLowResolution();
-        // TODO(b/149579527): Pass task size instead of computing scale.
-        // Assume width and height were scaled the same; compute scale only for width
-        scale = (float) thumbnail.getWidth() / snapshot.getTaskSize().x;
-        isRealSnapshot = snapshot.isRealSnapshot();
-        isTranslucent = snapshot.isTranslucent();
-        windowingMode = snapshot.getWindowingMode();
-        appearance = snapshot.getAppearance();
-        snapshotId = snapshot.getId();
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt
new file mode 100644
index 0000000..dcf7754
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.model
+
+import android.app.WindowConfiguration
+import android.content.res.Configuration
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.graphics.Color
+import android.graphics.Rect
+import android.util.Log
+import android.view.WindowInsetsController.Appearance
+import android.window.TaskSnapshot
+
+/** Data for a single thumbnail. */
+data class ThumbnailData(
+    val thumbnail: Bitmap? = null,
+    var orientation: Int = Configuration.ORIENTATION_UNDEFINED,
+    @JvmField var rotation: Int = WindowConfiguration.ROTATION_UNDEFINED,
+    @JvmField var insets: Rect = Rect(),
+    @JvmField var letterboxInsets: Rect = Rect(),
+    @JvmField var reducedResolution: Boolean = false,
+    @JvmField var isRealSnapshot: Boolean = true,
+    var isTranslucent: Boolean = false,
+    @JvmField var windowingMode: Int = WindowConfiguration.WINDOWING_MODE_UNDEFINED,
+    @JvmField @Appearance var appearance: Int = 0,
+    @JvmField var scale: Float = 1f,
+    var snapshotId: Long = 0,
+) {
+    fun recycleBitmap() {
+        thumbnail?.recycle()
+    }
+
+    companion object {
+        private fun makeThumbnail(snapshot: TaskSnapshot): Bitmap {
+            var thumbnail: Bitmap? = null
+            try {
+                snapshot.hardwareBuffer?.use { buffer ->
+                    thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.colorSpace)
+                }
+            } catch (ex: IllegalArgumentException) {
+                // TODO(b/157562905): Workaround for a crash when we get a snapshot without this
+                // state
+                Log.e(
+                    "ThumbnailData",
+                    "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " +
+                        "${snapshot.hardwareBuffer}",
+                    ex
+                )
+            }
+
+            return thumbnail
+                ?: Bitmap.createBitmap(snapshot.taskSize.x, snapshot.taskSize.y, ARGB_8888).apply {
+                    eraseColor(Color.BLACK)
+                }
+        }
+
+        @JvmStatic
+        fun wrap(taskIds: IntArray?, snapshots: Array<TaskSnapshot>?): HashMap<Int, ThumbnailData> {
+            return if (taskIds == null || snapshots == null || taskIds.size != snapshots.size) {
+                HashMap()
+            } else {
+                HashMap(taskIds.associateWith { taskId -> fromSnapshot(snapshots[taskId]) })
+            }
+        }
+
+        @JvmStatic
+        fun fromSnapshot(snapshot: TaskSnapshot): ThumbnailData {
+            val thumbnail = makeThumbnail(snapshot)
+            return ThumbnailData(
+                thumbnail = thumbnail,
+                insets = Rect(snapshot.contentInsets),
+                letterboxInsets = Rect(snapshot.letterboxInsets),
+                orientation = snapshot.orientation,
+                rotation = snapshot.rotation,
+                reducedResolution = snapshot.isLowResolution,
+                // TODO(b/149579527): Pass task size instead of computing scale.
+                // Assume width and height were scaled the same; compute scale only for width
+                scale = thumbnail.width.toFloat() / snapshot.taskSize.x,
+                isRealSnapshot = snapshot.isRealSnapshot,
+                isTranslucent = snapshot.isTranslucent,
+                windowingMode = snapshot.windowingMode,
+                appearance = snapshot.appearance,
+                snapshotId = snapshot.id,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ca63483..845ca5e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -147,7 +147,7 @@
             Log.w(TAG, "Failed to retrieve task snapshot", e);
         }
         if (snapshot != null) {
-            return new ThumbnailData(snapshot);
+            return ThumbnailData.fromSnapshot(snapshot);
         } else {
             return new ThumbnailData();
         }
@@ -167,7 +167,7 @@
             Log.w(TAG, "Failed to take task snapshot", e);
         }
         if (snapshot != null) {
-            return new ThumbnailData(snapshot);
+            return ThumbnailData.fromSnapshot(snapshot);
         } else {
             return new ThumbnailData();
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index a6e04ce..bbf4698 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -42,7 +42,7 @@
         try {
             final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId);
             if (snapshot != null) {
-                return new ThumbnailData(snapshot);
+                return ThumbnailData.fromSnapshot(snapshot);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to screenshot task", e);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 473719fa..cf8ec62 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -351,7 +351,7 @@
                     case ON_TASK_SNAPSHOT_CHANGED: {
                         Trace.beginSection("onTaskSnapshotChanged");
                         final TaskSnapshot snapshot = (TaskSnapshot) msg.obj;
-                        final ThumbnailData thumbnail = new ThumbnailData(snapshot);
+                        final ThumbnailData thumbnail = ThumbnailData.fromSnapshot(snapshot);
                         boolean snapshotConsumed = false;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             boolean consumed = mTaskStackListeners.get(i).onTaskSnapshotChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 1f04599..d5e911e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Flags;
 
 import java.util.HashMap;
 
@@ -339,15 +338,11 @@
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
         final PointF position = mMenuView.getMenuPosition();
         final PointF tuckedPosition = getTuckedMenuPosition();
-        if (Flags.floatingMenuAnimatedTuck()) {
-            flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
-                    Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY,
-                    FLING_FRICTION_SCALAR,
-                    createDefaultSpringForce(),
-                    tuckedPosition.x);
-        } else {
-            moveToPosition(tuckedPosition);
-        }
+        flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
+                Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY,
+                FLING_FRICTION_SCALAR,
+                createDefaultSpringForce(),
+                tuckedPosition.x);
 
         // Keep the touch region let users could click extra space to pop up the menu view
         // from the screen edge
@@ -359,23 +354,19 @@
     void moveOutEdgeAndShow() {
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
 
-        if (Flags.floatingMenuAnimatedTuck()) {
-            PointF position = mMenuView.getMenuPosition();
-            springMenuWith(DynamicAnimation.TRANSLATION_X,
-                    createDefaultSpringForce(),
-                    0,
-                    position.x,
-                    true
-            );
-            springMenuWith(DynamicAnimation.TRANSLATION_Y,
-                    createDefaultSpringForce(),
-                    0,
-                    position.y,
-                    true
-            );
-        } else {
-            mMenuView.onPositionChanged();
-        }
+        PointF position = mMenuView.getMenuPosition();
+        springMenuWith(DynamicAnimation.TRANSLATION_X,
+                createDefaultSpringForce(),
+                0,
+                position.x,
+                true
+        );
+        springMenuWith(DynamicAnimation.TRANSLATION_Y,
+                createDefaultSpringForce(),
+                0,
+                position.y,
+                true
+        );
 
         mMenuView.onEdgeChangedIfNeeded();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index be75e10..9d9e7df 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -321,22 +321,6 @@
         if (mMoveToTuckedListener != null) {
             mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
         }
-
-        if (!Flags.floatingMenuAnimatedTuck()) {
-            if (isMoveToTucked) {
-                final float halfWidth = getMenuWidth() / 2.0f;
-                final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
-                final Rect clipBounds = new Rect(
-                        (int) (!isOnLeftSide ? 0 : halfWidth),
-                        0,
-                        (int) (!isOnLeftSide ? halfWidth : getMenuWidth()),
-                        getMenuHeight()
-                );
-                setClipBounds(clipBounds);
-            } else {
-                setClipBounds(null);
-            }
-        }
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6dce1bb..0c67c50 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -322,9 +322,8 @@
         }
         addView(mMessageView, LayerIndex.MESSAGE_VIEW);
 
-        if (Flags.floatingMenuAnimatedTuck()) {
-            setClipChildren(true);
-        }
+        setClipChildren(true);
+
         setClickable(false);
         setFocusable(false);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -476,10 +475,8 @@
             mMenuAnimationController.startTuckedAnimationPreview();
         }
 
-        if (Flags.floatingMenuAnimatedTuck()) {
-            if (!mMenuView.isMoveToTucked()) {
-                setClipBounds(null);
-            }
+        if (!mMenuView.isMoveToTucked()) {
+            setClipBounds(null);
         }
         mMenuView.onArrivalAtPosition(false);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index cc52484..ca03a00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.events.AuthenticationSucceededInfo
 import android.hardware.face.FaceManager
 import android.hardware.fingerprint.FingerprintManager
+import android.util.Log
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.biometrics.shared.model.AuthenticationState
@@ -52,6 +53,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.shareIn
 
 /** A repository for the state of biometric authentication. */
@@ -85,6 +87,7 @@
     private val authenticationState: Flow<AuthenticationState> =
         conflatedCallbackFlow {
                 val updateAuthenticationState = { state: AuthenticationState ->
+                    Log.d(TAG, "authenticationState updated: $state")
                     trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
                 }
 
@@ -187,6 +190,7 @@
                         it.biometricSourceType == BiometricSourceType.FINGERPRINT)
             }
             .map { it.requestReason }
+            .onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") }
 
     override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
         authenticationState
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 40d38dd..6b61adc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -30,10 +30,10 @@
 import com.android.systemui.biometrics.shared.model.toSensorStrength
 import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
@@ -52,7 +53,7 @@
  */
 interface FingerprintPropertyRepository {
     /** Whether the fingerprint properties have been initialized yet. */
-    val propertiesInitialized: StateFlow<Boolean>
+    val propertiesInitialized: Flow<Boolean>
 
     /** The id of fingerprint sensor. */
     val sensorId: Flow<Int>
@@ -110,14 +111,8 @@
                 initialValue = UNINITIALIZED_PROPS,
             )
 
-    override val propertiesInitialized: StateFlow<Boolean> =
-        props
-            .map { it != UNINITIALIZED_PROPS }
-            .stateIn(
-                applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = props.value != UNINITIALIZED_PROPS,
-            )
+    override val propertiesInitialized: Flow<Boolean> =
+        props.map { it != UNINITIALIZED_PROPS }.onStart { emit(props.value != UNINITIALIZED_PROPS) }
 
     override val sensorId: Flow<Int> = props.map { it.sensorId }
 
@@ -141,7 +136,7 @@
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
-        private val UNINITIALIZED_PROPS =
+        val UNINITIALIZED_PROPS =
             FingerprintSensorPropertiesInternal(
                 -2 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 6e79e46..83aefca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.app.ActivityTaskManager
+import android.util.Log
 import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -26,6 +27,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onEach
 
 /** Encapsulates business logic for interacting with biometric authentication state. */
 interface BiometricStatusInteractor {
@@ -49,15 +51,20 @@
 
     override val sfpsAuthenticationReason: Flow<AuthenticationReason> =
         combine(
-            biometricStatusRepository.fingerprintAuthenticationReason,
-            fingerprintPropertyRepository.sensorType
-        ) { reason: AuthenticationReason, sensorType ->
-            if (sensorType.isPowerButton() && reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) {
-                reason
-            } else {
-                AuthenticationReason.NotRunning
+                biometricStatusRepository.fingerprintAuthenticationReason,
+                fingerprintPropertyRepository.sensorType
+            ) { reason: AuthenticationReason, sensorType ->
+                if (
+                    sensorType.isPowerButton() &&
+                        reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)
+                ) {
+                    reason
+                } else {
+                    AuthenticationReason.NotRunning
+                }
             }
-        }.distinctUntilChanged()
+            .distinctUntilChanged()
+            .onEach { Log.d(TAG, "sfpsAuthenticationReason updated: $it") }
 
     override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
         biometricStatusRepository.fingerprintAcquiredStatus
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 3112b67..d5b450d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -46,7 +46,7 @@
     displayStateInteractor: DisplayStateInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
-    val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+    val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized
     val isUdfps: StateFlow<Boolean> =
         repository.sensorType
             .map { it.isUdfps() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 4bdbfa2..ff7ac35 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.graphics.PorterDuff
 import android.graphics.PorterDuffColorFilter
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.WindowManager
@@ -91,6 +92,13 @@
                                     showIndicatorForDeviceEntry,
                                     progressBarIsVisible) =
                                     combinedFlows
+                                Log.d(
+                                    TAG,
+                                    "systemServerAuthReason = $systemServerAuthReason, " +
+                                        "showIndicatorForDeviceEntry = " +
+                                        "$showIndicatorForDeviceEntry, " +
+                                        "progressBarIsVisible = $progressBarIsVisible"
+                                )
                                 if (!isInRearDisplayMode) {
                                     if (progressBarIsVisible) {
                                         hide()
@@ -114,6 +122,10 @@
     /** Show the side fingerprint sensor indicator */
     private fun show() {
         if (overlayView?.isAttachedToWindow == true) {
+            Log.d(
+                TAG,
+                "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request"
+            )
             return
         }
 
@@ -128,6 +140,7 @@
             )
         bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
         overlayView!!.visibility = View.INVISIBLE
+        Log.d(TAG, "show(): adding overlayView $overlayView")
         windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
     }
 
@@ -137,6 +150,7 @@
             val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
             lottie.pauseAnimation()
             lottie.removeAllLottieOnCompositionLoadedListener()
+            Log.d(TAG, "hide(): removing overlayView $overlayView, setting to null")
             windowManager.get().removeView(overlayView)
             overlayView = null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
index c018ecb..0544a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -66,7 +68,18 @@
                         user.id
                     )
                     ?.let { PolicyRestriction.Restricted(it) }
-                    ?: PolicyRestriction.NoRestriction
+                    ?: if (
+                        enforceBrightnessBaseUserRestriction() &&
+                            userRestrictionChecker.hasBaseUserRestriction(
+                                applicationContext,
+                                UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+                                user.id
+                            )
+                    ) {
+                        PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+                    } else {
+                        PolicyRestriction.NoRestriction
+                    }
             }
             .flowOn(backgroundDispatcher)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 06c8396..9599a88 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -61,7 +61,6 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
@@ -130,7 +129,7 @@
         allOf(
                 communalSettingsInteractor.isCommunalEnabled,
                 not(keyguardInteractor.isEncryptedOrLockdown),
-                anyOf(keyguardInteractor.isKeyguardShowing, keyguardInteractor.isDreaming)
+                keyguardInteractor.isKeyguardShowing
             )
             .distinctUntilChanged()
             .onEach { available ->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index f6122ad..c0dc313 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -96,6 +96,8 @@
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
     }
 
+    val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal
+
     /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
     suspend fun onOpenWidgetPicker(
         resources: Resources,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index f20fafc..426f484 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
 import javax.inject.Inject
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /** An Activity for editing the widgets that appear in hub mode. */
@@ -69,6 +70,8 @@
 
     private var shouldOpenWidgetPickerOnStart = false
 
+    private var lockOnDestroy = false
+
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
@@ -149,15 +152,18 @@
     }
 
     private fun onEditDone() {
-        try {
+        lifecycleScope.launch {
             communalViewModel.changeScene(
                 CommunalScenes.Communal,
                 CommunalTransitionKeys.SimpleFade
             )
-            checkNotNull(windowManagerService).lockNow(/* options */ null)
+
+            // Wait for the current scene to be idle on communal.
+            communalViewModel.isIdleOnCommunal.first { it }
+            // Then finish the activity (this helps to avoid a flash of lockscreen when locking
+            // in onDestroy()).
+            lockOnDestroy = true
             finish()
-        } catch (e: RemoteException) {
-            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
         }
     }
 
@@ -190,5 +196,15 @@
     override fun onDestroy() {
         super.onDestroy()
         communalViewModel.setEditModeOpen(false)
+
+        if (lockOnDestroy) lockNow()
+    }
+
+    private fun lockNow() {
+        try {
+            checkNotNull(windowManagerService).lockNow(/* options */ null)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
deleted file mode 100644
index 4e40042..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.Activity
-import android.os.Bundle
-import javax.inject.Inject
-
-/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */
-class ContrastDialogActivity
-@Inject
-constructor(
-    private val contrastDialogDelegate : ContrastDialogDelegate
-) : Activity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        contrastDialogDelegate.createDialog().show()
-        finish()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
deleted file mode 100644
index 0daa058..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.app.UiModeManager.ContrastUtils.toContrastLevel
-import android.os.Bundle
-import android.provider.Settings
-import android.view.View
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.settings.SecureSettings
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/** Dialog to select contrast options */
-class ContrastDialogDelegate
-@Inject
-constructor(
-    private val sysuiDialogFactory: SystemUIDialog.Factory,
-    @Main private val mainExecutor: Executor,
-    private val uiModeManager: UiModeManager,
-    private val userTracker: UserTracker,
-    private val secureSettings: SecureSettings,
-) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener {
-
-    @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
-    lateinit var dialogView: View
-    @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
-
-    override fun createDialog(): SystemUIDialog {
-        val dialog = sysuiDialogFactory.create(this)
-        dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null)
-        with(dialog) {
-            setView(dialogView)
-
-            setTitle(R.string.quick_settings_contrast_label)
-            setNeutralButton(R.string.cancel) { _, _ ->
-                secureSettings.putFloatForUser(
-                    Settings.Secure.CONTRAST_LEVEL,
-                    initialContrast,
-                    userTracker.userId
-                )
-                dialog.dismiss()
-            }
-            setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() }
-        }
-
-        return dialog
-    }
-
-    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
-        contrastButtons =
-            mapOf(
-                CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high)
-            )
-
-        contrastButtons.forEach { (contrastLevel, contrastButton) ->
-            contrastButton.setOnClickListener {
-                val contrastValue = fromContrastLevel(contrastLevel)
-                secureSettings.putFloatForUser(
-                    Settings.Secure.CONTRAST_LEVEL,
-                    contrastValue,
-                    userTracker.userId
-                )
-            }
-        }
-
-        initialContrast = uiModeManager.contrast
-        highlightContrast(toContrastLevel(initialContrast))
-    }
-
-    override fun onStart(dialog: SystemUIDialog) {
-        uiModeManager.addContrastChangeListener(mainExecutor, this)
-    }
-
-    override fun onStop(dialog: SystemUIDialog) {
-        uiModeManager.removeContrastChangeListener(this)
-    }
-
-    override fun onContrastChanged(contrast: Float) {
-        highlightContrast(toContrastLevel(contrast))
-    }
-
-    private fun highlightContrast(contrast: Int) {
-        contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index d2df276..c2e1e33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.communal.widgets.EditWidgetsActivity;
-import com.android.systemui.contrast.ContrastDialogActivity;
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -72,12 +71,6 @@
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
 
-    /** Inject into ContrastDialogActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(ContrastDialogActivity.class)
-    public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity);
-
     /** Inject into UsbDebuggingActivity. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index eef4b97..9626077 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Context
+import android.util.Log
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -39,6 +40,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 /**
@@ -96,10 +98,13 @@
                     keyguardUpdateMonitor.isFingerprintDetectionRunning &&
                     keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
             }
+            .onEach { Log.d(TAG, "showIndicatorForPrimaryBouncer updated: $it") }
 
     private val showIndicatorForAlternateBouncer: Flow<Boolean> =
         // Note: this interactor internally verifies that SideFPS is enabled and running.
-        alternateBouncerInteractor.isVisible
+        alternateBouncerInteractor.isVisible.onEach {
+            Log.d(TAG, "showIndicatorForAlternateBouncer updated: $it")
+        }
 
     /**
      * Indicates whether the primary or alternate bouncers request showing the side fingerprint
@@ -112,6 +117,7 @@
                 showForPrimaryBouncer || showForAlternateBouncer
             }
             .distinctUntilChanged()
+            .onEach { Log.d(TAG, "showIndicatorForDeviceEntry updated: $it") }
 
     private fun isBouncerActive(): Boolean {
         if (SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 857096e..b1ef76e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Context
+import android.util.Log
 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -42,6 +43,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -78,7 +80,15 @@
     private val refreshEvents: Flow<Unit> =
         merge(
             configurationInteractor.onAnyConfigurationChange,
-            fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map { Unit },
+            fingerprintPropertyInteractor.propertiesInitialized
+                .filter { it }
+                .map { Unit }
+                .onEach {
+                    Log.d(
+                        "KeyguardBlueprintInteractor",
+                        "triggering refreshEvent from fpPropertiesInitialized"
+                    )
+                },
         )
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 229e592..19e3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -308,7 +308,11 @@
      * It will be called when the container is out of view.
      */
     lateinit var updateUserVisibility: () -> Unit
-    lateinit var updateHostVisibility: () -> Unit
+    var updateHostVisibility: () -> Unit = {}
+        set(value) {
+            field = value
+            mediaCarouselViewModel.updateHostVisibility = value
+        }
 
     private val isReorderingAllowed: Boolean
         get() = visualStabilityProvider.isReorderingAllowed
@@ -345,6 +349,20 @@
         configurationController.addCallback(configListener)
         if (!mediaFlags.isMediaControlsRefactorEnabled()) {
             setUpListeners()
+        } else {
+            val visualStabilityCallback = OnReorderingAllowedListener {
+                mediaCarouselViewModel.onReorderingAllowed()
+
+                // Update user visibility so that no extra impression will be logged when
+                // activeMediaIndex resets to 0
+                if (this::updateUserVisibility.isInitialized) {
+                    updateUserVisibility()
+                }
+
+                // Let's reset our scroll position
+                mediaCarouselScrollHandler.scrollToStart()
+            }
+            visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
         }
         mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
             // The pageIndicator is not laid out yet when we get the current state update,
@@ -366,10 +384,6 @@
         )
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
         mediaCarousel.repeatWhenAttached {
-            if (mediaFlags.isMediaControlsRefactorEnabled()) {
-                mediaCarouselViewModel.onAttached()
-                mediaCarouselScrollHandler.scrollToStart()
-            }
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 listenForAnyStateToGoneKeyguardTransition(this)
                 listenForAnyStateToLockscreenTransition(this)
@@ -592,9 +606,7 @@
                         if (!immediately) {
                             // Although it wasn't requested, we were able to process the removal
                             // immediately since reordering is allowed. So, notify hosts to update
-                            if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
-                                updateHostVisibility()
-                            }
+                            updateHostVisibility()
                         }
                     } else {
                         keysNeedRemoval.add(key)
@@ -751,6 +763,7 @@
             }
         }
         viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+        controllerByViewModel[commonViewModel] = viewController
         updateViewControllerToState(viewController, noAnimation = true)
         updatePageIndicator()
         if (
@@ -764,7 +777,6 @@
         mediaCarouselScrollHandler.onPlayersChanged()
         mediaFrame.requiresRemeasuring = true
         commonViewModel.onAdded(commonViewModel)
-        controllerByViewModel[commonViewModel] = viewController
     }
 
     private fun onUpdated(commonViewModel: MediaCommonViewModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index fd5f445..4e90936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -57,12 +57,12 @@
     val mediaItems: StateFlow<List<MediaCommonViewModel>> =
         interactor.currentMedia
             .map { sortedItems ->
-                buildList {
+                val mediaList = buildList {
                     sortedItems.forEach { commonModel ->
                         // When view is started we should make sure to clean models that are pending
                         // removal.
                         // This action should only be triggered once.
-                        if (!isAttached || !modelsPendingRemoval.contains(commonModel)) {
+                        if (!allowReorder || !modelsPendingRemoval.contains(commonModel)) {
                             when (commonModel) {
                                 is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
                                 is MediaCommonModel.MediaRecommendations ->
@@ -70,11 +70,16 @@
                             }
                         }
                     }
-                    if (isAttached) {
-                        modelsPendingRemoval.clear()
-                    }
-                    isAttached = false
                 }
+                if (allowReorder) {
+                    if (modelsPendingRemoval.size > 0) {
+                        updateHostVisibility()
+                    }
+                    modelsPendingRemoval.clear()
+                }
+                allowReorder = false
+
+                mediaList
             }
             .stateIn(
                 scope = applicationScope,
@@ -82,6 +87,8 @@
                 initialValue = emptyList(),
             )
 
+    var updateHostVisibility: () -> Unit = {}
+
     private val mediaControlByInstanceId =
         mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
 
@@ -89,15 +96,15 @@
 
     private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
 
-    private var isAttached = false
+    private var allowReorder = false
 
     fun onSwipeToDismiss() {
         logger.logSwipeDismiss()
         interactor.onSwipeToDismiss()
     }
 
-    fun onAttached() {
-        isAttached = true
+    fun onReorderingAllowed() {
+        allowReorder = true
         interactor.reorderMedia()
     }
 
@@ -194,7 +201,11 @@
     ) {
         if (immediatelyRemove || isReorderingAllowed()) {
             interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
-            // TODO if not immediate remove update host visibility
+            if (!immediatelyRemove) {
+                // Although it wasn't requested, we were able to process the removal
+                // immediately since reordering is allowed. So, notify hosts to update
+                updateHostVisibility()
+            }
         } else {
             modelsPendingRemoval.add(commonModel)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index 412c006..9265bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -140,10 +140,11 @@
 
         val bitmapShader = bitmapShader ?: return
         val thumbnailData = thumbnailData ?: return
+        val thumbnail = thumbnailData.thumbnail ?: return
         val display = context.display ?: return
         val windowMetrics = windowManager.maximumWindowMetrics
 
-        previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
+        previewRect.set(0, 0, thumbnail.width, thumbnail.height)
 
         val currentRotation: Int = display.rotation
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3907a72..5e6ee4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -16,12 +16,20 @@
 
 package com.android.systemui.qrcodescanner.dagger
 
+import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
 import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
@@ -54,5 +62,24 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
+
+        /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
+        @Provides
+        @IntoMap
+        @StringKey(QR_CODE_SCANNER_TILE_SPEC)
+        fun provideQRCodeScannerTileViewModel(
+            factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>,
+            mapper: QRCodeScannerTileMapper,
+            stateInteractor: QRCodeScannerTileDataInteractor,
+            userActionInteractor: QRCodeScannerTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(QR_CODE_SCANNER_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 2d3120a..972b20e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -29,11 +29,15 @@
 
 /**
  * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
- * dismissing and tile from-view animations.
+ * dismissing and tile from-view animations, as well as the option to show over lockscreen.
  */
 interface QSTileIntentUserInputHandler {
 
-    fun handle(expandable: Expandable?, intent: Intent)
+    fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        dismissShadeShowOverLockScreenWhenLocked: Boolean = false
+    )
 
     /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
     fun handle(
@@ -52,12 +56,25 @@
     private val userHandle: UserHandle,
 ) : QSTileIntentUserInputHandler {
 
-    override fun handle(expandable: Expandable?, intent: Intent) {
+    override fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        dismissShadeShowOverLockScreenWhenLocked: Boolean
+    ) {
         val animationController: ActivityTransitionAnimator.Controller? =
             expandable?.activityTransitionController(
                 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
             )
-        activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+        if (dismissShadeShowOverLockScreenWhenLocked) {
+            activityStarter.startActivity(
+                intent,
+                true /* dismissShade */,
+                animationController,
+                true /* showOverLockscreenWhenLocked */
+            )
+        } else {
+            activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+        }
     }
 
     // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
new file mode 100644
index 0000000..1e8ce58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */
+class QRCodeScannerTileDataInteractor
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val qrController: QRCodeScannerController,
+) : QSTileDataInteractor<QRCodeScannerTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<QRCodeScannerTileModel> =
+        conflatedCallbackFlow {
+                qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE)
+                val callback =
+                    object : QRCodeScannerController.Callback {
+                        override fun onQRCodeScannerActivityChanged() {
+                            trySend(generateModel())
+                        }
+                    }
+                qrController.addCallback(callback)
+                awaitClose {
+                    qrController.removeCallback(callback)
+                    qrController.unregisterQRCodeScannerChangeObservers(
+                        DEFAULT_QR_CODE_SCANNER_CHANGE
+                    )
+                }
+            }
+            .onStart { emit(generateModel()) }
+            .flowOn(bgCoroutineContext)
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                QRCodeScannerTileModel.TemporarilyUnavailable
+            )
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(qrController.isCameraAvailable)
+
+    private fun generateModel(): QRCodeScannerTileModel {
+        val intent = qrController.intent
+
+        return if (qrController.isAbleToLaunchScannerActivity && intent != null)
+            QRCodeScannerTileModel.Available(intent)
+        else QRCodeScannerTileModel.TemporarilyUnavailable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
new file mode 100644
index 0000000..7c0c41e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.domain.interactor
+
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles qr tile clicks. */
+class QRCodeScannerTileUserActionInteractor
+@Inject
+constructor(
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<QRCodeScannerTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    when (data) {
+                        is QRCodeScannerTileModel.Available ->
+                            qsTileIntentUserActionHandler.handle(
+                                action.expandable,
+                                data.intent,
+                                true
+                            )
+                        is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op
+                    }
+                }
+                is QSTileUserAction.LongClick -> {} // no-op
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
new file mode 100644
index 0000000..22c9b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.domain.model
+
+import android.content.Intent
+
+/** qr scanner tile model. */
+sealed interface QRCodeScannerTileModel {
+    data class Available(val intent: Intent) : QRCodeScannerTileModel
+    data object TemporarilyUnavailable : QRCodeScannerTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
new file mode 100644
index 0000000..45a7717
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
+class QRCodeScannerTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+
+    override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.qr_code_scanner_title)
+            contentDescription = label
+            icon = {
+                Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
+            }
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+            when (data) {
+                is QRCodeScannerTileModel.Available -> {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    secondaryLabel = null
+                }
+                is QRCodeScannerTileModel.TemporarilyUnavailable -> {
+                    activationState = QSTileState.ActivationState.UNAVAILABLE
+                    secondaryLabel =
+                        resources.getString(R.string.qr_code_scanner_updating_secondary_label)
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6bb30c7..32e383a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -253,6 +253,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -449,6 +450,9 @@
     private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
     private final ShadeFoldAnimatorImpl mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
 
+    @VisibleForTesting
+    Set<Animator> mTestSetOfAnimatorsUsed;
+
     private boolean mShowIconsWhenExpanded;
     private int mIndicationBottomPadding;
     private int mAmbientIndicationBottomPadding;
@@ -4149,6 +4153,8 @@
     }
 
     private void setAnimator(ValueAnimator animator) {
+        // TODO(b/341163515): Should we clean up the old animator?
+        registerAnimatorForTest(animator);
         mHeightAnimator = animator;
         if (animator == null && mPanelUpdateWhenAnimatorEnds) {
             mPanelUpdateWhenAnimatorEnds = false;
@@ -4193,6 +4199,7 @@
     private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
         float startExpansion = mOverExpansion;
         ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+        registerAnimatorForTest(animator);
         animator.addUpdateListener(
                 animation -> {
                     if (overshootAmount > 0.0f
@@ -4210,6 +4217,12 @@
         return animator;
     }
 
+    private void registerAnimatorForTest(Animator animator) {
+        if (mTestSetOfAnimatorsUsed != null) {
+            mTestSetOfAnimatorsUsed.add(animator);
+        }
+    }
+
     /** Update the visibility of {@link NotificationPanelView} if necessary. */
     private void updateVisibility() {
         mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 968b591..5a616df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,7 +18,7 @@
 
 import static android.graphics.PorterDuff.Mode.SRC_ATOP;
 
-import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization;
 import static com.android.systemui.util.ColorUtilKt.hexColorString;
 
 import android.annotation.ColorInt;
@@ -407,7 +407,7 @@
         final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final @ColorInt int scHigh;
-        if (!notificationBackgroundTintOptimization()) {
+        if (!notificationFooterBackgroundTintOptimization()) {
             scHigh = Utils.getColorAttrDefaultColor(mContext,
                     com.android.internal.R.attr.materialColorSurfaceContainerHigh);
             if (scHigh != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index d4f8ea3..d6c73a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -23,8 +23,8 @@
 /** Helper for reading or using the heads-up cycling flag state. */
 @Suppress("NOTHING_TO_INLINE")
 object NotificationHeadsUpCycling {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING
+    /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -33,7 +33,7 @@
     /** Is the heads-up cycling animation enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationHeadsUpCycling()
+        get() = Flags.notificationThrottleHun()
 
     /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
     @JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index d4b2dbf..2e54972 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -53,6 +53,27 @@
         )
     }
 
+    fun logTopLevelServiceStateBroadcastEmergencyOnly(subId: Int, serviceState: ServiceState) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                bool1 = serviceState.isEmergencyOnly
+            },
+            { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" }
+        )
+    }
+
+    fun logTopLevelServiceStateBroadcastMissingExtras(subId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = subId },
+            { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" }
+        )
+    }
+
     fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
new file mode 100644
index 0000000..cce3eb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.model
+
+import android.telephony.ServiceState
+
+/**
+ * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to
+ * extract from service state here for consumption downstream
+ */
+data class ServiceStateModel(val isEmergencyOnly: Boolean) {
+    companion object {
+        fun fromServiceState(serviceState: ServiceState): ServiceStateModel {
+            return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 9471574..5ad8bf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -21,6 +21,7 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -92,6 +93,19 @@
     val defaultMobileIconGroup: Flow<MobileIconGroup>
 
     /**
+     * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a
+     * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]).
+     *
+     * While each [MobileConnectionsRepository] listens for the service state of each subscription,
+     * there is potentially a service state associated with the device itself. This value can be
+     * used to calculate e.g., the emergency calling capability of the device (as opposed to the
+     * emergency calling capability of an individual mobile connection)
+     *
+     * Note: this is a [StateFlow] using an eager sharing strategy.
+     */
+    val deviceServiceState: StateFlow<ServiceStateModel?>
+
+    /**
      * If any active SIM on the device is in
      * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
      * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 8a8e33e..b068152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
@@ -151,6 +152,15 @@
     override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
         activeRepo.flatMapLatest { it.defaultMobileIconGroup }
 
+    override val deviceServiceState: StateFlow<ServiceStateModel?> =
+        activeRepo
+            .flatMapLatest { it.deviceServiceState }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.deviceServiceState.value
+            )
+
     override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
     override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 2b3c632..a944e91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -136,6 +137,9 @@
 
     override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
 
+    // TODO(b/339023069): demo command for device-based connectivity state
+    override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null)
+
     override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())
     override fun getIsAnySimSecure(): Boolean = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 0073e9c..c32f0e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -18,8 +18,10 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.content.Intent
 import android.content.IntentFilter
 import android.telephony.CarrierConfigManager
+import android.telephony.ServiceState
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -35,7 +37,6 @@
 import com.android.settingslib.mobile.MobileMappings.Config
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -47,6 +48,7 @@
 import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -55,6 +57,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
 import javax.inject.Inject
@@ -68,6 +71,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
@@ -169,6 +173,35 @@
             }
             .flowOn(bgDispatcher)
 
+    /** Note that this flow is eager, so we don't miss any state */
+    override val deviceServiceState: StateFlow<ServiceStateModel?> =
+        broadcastDispatcher
+            .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
+                val subId =
+                    intent.getIntExtra(
+                        SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                        INVALID_SUBSCRIPTION_ID
+                    )
+
+                val extras = intent.extras
+                if (extras == null) {
+                    logger.logTopLevelServiceStateBroadcastMissingExtras(subId)
+                    return@broadcastFlow null
+                }
+
+                val serviceState = ServiceState.newFromBundle(extras)
+                logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState)
+                if (subId == INVALID_SUBSCRIPTION_ID) {
+                    // Assume that -1 here is the device's service state. We don't care about
+                    // other ones.
+                    ServiceStateModel.fromServiceState(serviceState)
+                } else {
+                    null
+                }
+            }
+            .filterNotNull()
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
     /**
      * State flow that emits the set of mobile data subscriptions, each represented by its own
      * [SubscriptionModel].
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 91d7ca6..cc4d568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -111,6 +111,13 @@
     val isForceHidden: Flow<Boolean>
 
     /**
+     * True if the device-level service state (with -1 subscription id) reports emergency calls
+     * only. This value is only useful when there are no other subscriptions OR all existing
+     * subscriptions report that they are not in service.
+     */
+    val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean>
+
+    /**
      * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
      * subId.
      */
@@ -377,6 +384,9 @@
             .map { it.contains(ConnectivitySlot.MOBILE) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
+        mobileConnectionsRepo.deviceServiceState.map { it?.isEmergencyOnly ?: false }
+
     /** Vends out new [MobileIconInteractor] for a particular subId */
     override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
         reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 51c053e..5b954b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -19,6 +19,9 @@
 import com.android.internal.telephony.flags.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -45,6 +48,7 @@
     deviceProvisioningInteractor: DeviceProvisioningInteractor,
     wifiInteractor: WifiInteractor,
     @Application scope: CoroutineScope,
+    @OemSatelliteInputLog private val logBuffer: LogBuffer,
 ) {
     /** Must be observed by any UI showing Satellite iconography */
     val isSatelliteAllowed =
@@ -79,25 +83,52 @@
     val isWifiActive: Flow<Boolean> =
         wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
 
+    private val allConnectionsOos =
+        iconsInteractor.icons.aggregateOver(
+            selector = { intr ->
+                combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
+                    isInService,
+                    isEmergencyOnly,
+                    isNtn ->
+                    !isInService && !isEmergencyOnly && !isNtn
+                }
+            },
+            defaultValue = true, // no connections == everything is OOS
+        ) { isOosAndNotEmergencyAndNotSatellite ->
+            isOosAndNotEmergencyAndNotSatellite.all { it }
+        }
+
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
-                iconsInteractor.icons.aggregateOver(
-                    selector = { intr ->
-                        combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
-                            isInService,
-                            isEmergencyOnly,
-                            isNtn ->
-                            !isInService && !(isEmergencyOnly || isNtn)
-                        }
-                    }
-                ) { isOosAndNotEmergencyOnlyOrSatellite ->
-                    isOosAndNotEmergencyOnlyOrSatellite.all { it }
+                combine(
+                    allConnectionsOos,
+                    iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
+                ) { connectionsOos, deviceEmergencyOnly ->
+                    logBuffer.log(
+                        TAG,
+                        LogLevel.INFO,
+                        {
+                            bool1 = connectionsOos
+                            bool2 = deviceEmergencyOnly
+                        },
+                        {
+                            "Updating OOS status. allConnectionsOOs=$bool1 " +
+                                "deviceEmergencyOnly=$bool2"
+                        },
+                    )
+                    // If no connections exist, or all are OOS, then we look to the device-based
+                    // service state to detect if any calls are possible
+                    connectionsOos && !deviceEmergencyOnly
                 }
             } else {
                 flowOf(false)
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+    companion object {
+        const val TAG = "DeviceBasedSatelliteInteractor"
+    }
 }
 
 /**
@@ -106,12 +137,22 @@
  *
  * Provides a way to connect the reactivity of the top-level flow with the reactivity of an
  * arbitrarily-defined relationship ([selector]) from R to the flow that R exposes.
+ *
+ * [defaultValue] allows for a default value to be used if there are no leaf nodes after applying
+ * [selector]. E.g., if there are no mobile connections, assume that there is no service.
  */
 @OptIn(ExperimentalCoroutinesApi::class)
 private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver(
     crossinline selector: (R) -> Flow<S>,
-    crossinline transform: (Array<S>) -> T
+    defaultValue: T,
+    crossinline transform: (Array<S>) -> T,
 ): Flow<T> {
     return map { list -> list.map { selector(it) } }
-        .flatMapLatest { newFlows -> combine(newFlows) { newVals -> transform(newVals) } }
+        .flatMapLatest { newFlows ->
+            if (newFlows.isEmpty()) {
+                flowOf(defaultValue)
+            } else {
+                combine(newFlows) { newVals -> transform(newVals) }
+            }
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index e0df1e0..2d5e3a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.graphics.PointF;
-import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -40,7 +39,6 @@
 import androidx.dynamicanimation.animation.SpringForce;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.utils.TestUtils;
@@ -230,7 +228,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
     public void tuck_animates() {
         mMenuAnimationController.cancelAnimations();
         mMenuAnimationController.moveToEdgeAndHide();
@@ -239,7 +236,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
     public void untuck_animates() {
         mMenuAnimationController.cancelAnimations();
         mMenuAnimationController.moveOutEdgeAndShow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
deleted file mode 100644
index ab03465..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.os.Looper
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.model.SysUiState
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class ContrastDialogDelegateTest : SysuiTestCase() {
-
-    private val mainExecutor = FakeExecutor(FakeSystemClock())
-    private lateinit var mContrastDialogDelegate: ContrastDialogDelegate
-    @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
-    @Mock private lateinit var sysuiDialog: SystemUIDialog
-    @Mock private lateinit var mockUiModeManager: UiModeManager
-    @Mock private lateinit var mockUserTracker: UserTracker
-    @Mock private lateinit var mockSecureSettings: SecureSettings
-    @Mock private lateinit var sysuiState: SysUiState
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags())
-        mDependency.injectTestDependency(SysUiState::class.java, sysuiState)
-        mDependency.injectMockDependency(DialogTransitionAnimator::class.java)
-        whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState)
-        whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java)))
-            .thenReturn(sysuiDialog)
-        whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext))
-
-        whenever(mockUserTracker.userId).thenReturn(context.userId)
-        if (Looper.myLooper() == null) Looper.prepare()
-
-        mContrastDialogDelegate =
-            ContrastDialogDelegate(
-                sysuiDialogFactory,
-                mainExecutor,
-                mockUiModeManager,
-                mockUserTracker,
-                mockSecureSettings
-            )
-
-        mContrastDialogDelegate.createDialog()
-        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
-        verify(sysuiDialog).setView(viewCaptor.capture())
-        whenever(sysuiDialog.requireViewById(anyInt()) as View?).then {
-            viewCaptor.value.requireViewById(it.getArgument(0))
-        }
-    }
-
-    @Test
-    fun testClickButtons_putsContrastInSettings() {
-        mContrastDialogDelegate.onCreate(sysuiDialog, null)
-
-        mContrastDialogDelegate.contrastButtons.forEach {
-            (contrastLevel: Int, clickedButton: FrameLayout) ->
-            clickedButton.performClick()
-            mainExecutor.runAllReady()
-            verify(mockSecureSettings)
-                .putFloatForUser(
-                    eq(Settings.Secure.CONTRAST_LEVEL),
-                    eq(fromContrastLevel(contrastLevel)),
-                    eq(context.userId)
-                )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
index db275ec..db36131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -52,7 +52,7 @@
             val taskId = 123
             val isLowResolution = false
             val snapshot = createTaskSnapshot()
-            val thumbnailData = ThumbnailData(snapshot)
+            val thumbnailData = ThumbnailData.fromSnapshot(snapshot)
             whenever(activityManager.getTaskThumbnail(taskId, isLowResolution))
                 .thenReturn(thumbnailData)
 
@@ -74,7 +74,7 @@
     fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() =
         testScope.runTest {
             val taskId = 321
-            val thumbnailData = ThumbnailData(createTaskSnapshot())
+            val thumbnailData = ThumbnailData.fromSnapshot(createTaskSnapshot())
 
             whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8e32907..4d32cc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.Animator;
 import android.annotation.IdRes;
 import android.content.ContentResolver;
 import android.content.res.Configuration;
@@ -207,12 +208,15 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.mockito.stubbing.Answer;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
 
@@ -387,9 +391,11 @@
 
     protected FragmentHostManager.FragmentListener mFragmentListener;
 
+    @Rule(order = 200)
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
         mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
 
@@ -761,6 +767,9 @@
                     @Override
                     public void onOpenStarted() {}
                 });
+        // Create a set to which the class will add all animators used, so that we can
+        // verify that they are all stopped.
+        mNotificationPanelViewController.mTestSetOfAnimatorsUsed = new HashSet<>();
         ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
         verify(mView, atLeast(1)).addOnAttachStateChangeListener(
@@ -822,13 +831,20 @@
 
     @After
     public void tearDown() {
+        List<Animator> leakedAnimators = null;
         if (mNotificationPanelViewController != null) {
             mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
             mNotificationPanelViewController.cancelHeightAnimator();
+            leakedAnimators = mNotificationPanelViewController.mTestSetOfAnimatorsUsed.stream()
+                    .filter(Animator::isRunning).toList();
+            mNotificationPanelViewController.mTestSetOfAnimatorsUsed.forEach(Animator::cancel);
         }
         if (mMainHandler != null) {
             mMainHandler.removeCallbacksAndMessages(null);
         }
+        if (leakedAnimators != null) {
+            assertThat(leakedAnimators).isEmpty();
+        }
     }
 
     protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e1cdda4..6536405 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -705,6 +705,7 @@
     }
 
     @Test
+    @Ignore("b/341163515 - fails to clean up animators correctly")
     public void testSwipeWhileLocked_notifiesKeyguardState() {
         mStatusBarStateController.setState(KEYGUARD);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 36df61d..96e599f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.annotation.SuppressLint
 import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.Network
@@ -27,8 +28,10 @@
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
+import android.os.Bundle
 import android.os.ParcelUuid
 import android.telephony.CarrierConfigManager
+import android.telephony.ServiceState
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -53,6 +56,7 @@
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -595,6 +599,51 @@
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
         }
 
+    @SuppressLint("UnspecifiedRegisterReceiverFlag")
+    @Test
+    fun testDeviceServiceStateFromBroadcast_eagerlyWatchesBroadcast() =
+        testScope.runTest {
+            // Value starts out empty (null)
+            assertThat(underTest.deviceServiceState.value).isNull()
+
+            // WHEN an appropriate intent gets sent out
+            val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                intent,
+            )
+            runCurrent()
+
+            // THEN the repo's state is updated
+            val expected = ServiceStateModel(isEmergencyOnly = false)
+            assertThat(underTest.deviceServiceState.value).isEqualTo(expected)
+        }
+
+    @Test
+    fun testDeviceServiceStateFromBroadcast_followsSubIdNegativeOne() =
+        testScope.runTest {
+            // device based state tracks -1
+            val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                intent,
+            )
+            runCurrent()
+
+            val deviceBasedState = ServiceStateModel(isEmergencyOnly = false)
+            assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+
+            // ... and ignores any other subId
+            val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                intent2,
+            )
+            runCurrent()
+
+            assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+        }
+
     @Test
     @Ignore("b/333912012")
     fun testConnectionCache_clearsInvalidSubscriptions() =
@@ -1491,5 +1540,24 @@
                 whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)
                 whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
             }
+
+        /**
+         * To properly mimic telephony manager, create a service state, and then turn it into an
+         * intent
+         */
+        private fun serviceStateIntent(
+            subId: Int,
+            emergencyOnly: Boolean = false,
+        ): Intent {
+            val serviceState = ServiceState().apply { isEmergencyOnly = emergencyOnly }
+
+            val bundle = Bundle()
+            serviceState.fillInNotifierBundle(bundle)
+
+            return Intent(Intent.ACTION_SERVICE_STATE).apply {
+                putExtras(bundle)
+                putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 0f9cbfa..58d9ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -888,6 +889,22 @@
             assertThat(interactor1).isSameInstanceAs(interactor2)
         }
 
+    @Test
+    fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
+
+            connectionsRepository.deviceServiceState.value =
+                ServiceStateModel(isEmergencyOnly = true)
+
+            assertThat(latest).isTrue()
+
+            connectionsRepository.deviceServiceState.value =
+                ServiceStateModel(isEmergencyOnly = false)
+
+            assertThat(latest).isFalse()
+        }
+
     /**
      * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
      * flow.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index 405e3ed..d303976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
@@ -71,6 +72,7 @@
                 deviceProvisioningInteractor,
                 wifiInteractor,
                 testScope.backgroundScope,
+                FakeLogBuffer.Factory.create(),
             )
     }
 
@@ -114,6 +116,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -162,6 +165,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.connectionState)
@@ -218,6 +222,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.signalStrength)
@@ -238,25 +243,97 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_noConnections_yes() =
+    fun areAllConnectionsOutOfService_noConnections_noDeviceEmergencyCalls_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 0 connections
 
+            // GIVEN, device is not in emergency calls only mode
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
+
             // THEN the value is propagated to this interactor
             assertThat(latest).isTrue()
         }
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() =
+    fun areAllConnectionsOutOfService_noConnections_deviceEmergencyCalls_yes() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 0 connections
+
+            // GIVEN, device is in emergency calls only mode
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_noDeviceEmergencyCalls_yes() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
+
+            // WHEN connection is in service
+            i1.isInService.value = true
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+
+            // THEN we are considered NOT to be OOS
+            assertThat(latest).isFalse()
+
+            // WHEN the connection disappears
+            iconsInteractor.icons.value = listOf()
+
+            // THEN we are back to OOS
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_deviceEmergencyCalls_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // WHEN one connection is in service
+            i1.isInService.value = true
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+
+            // THEN we are considered NOT to be OOS
+            assertThat(latest).isFalse()
+
+            // WHEN the connection disappears
+            iconsInteractor.icons.value = listOf()
+
+            // THEN we are still NOT in OOS, due to device-based emergency calls
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_noDeviceEmergencyCalls_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 2 connections
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
             val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
 
             // WHEN all of the connections are OOS and none are NTN
             i1.isInService.value = false
@@ -272,13 +349,39 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() =
+    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_deviceEmergencyCalls_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 2 connections
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
             val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+            // GIVEN, device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // WHEN all of the connections are OOS and none are NTN
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+            i2.isInService.value = false
+            i2.isEmergencyOnly.value = false
+            i2.isNonTerrestrial.value = false
+
+            // THEN we are not considered OOS due to device based emergency calling
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_twoConnectionsOos_noDeviceEmergencyCalls_oneNtn_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 2 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
 
             // WHEN all of the connections are OOS and one is NTN
             i1.isInService.value = false
@@ -296,12 +399,14 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() =
+    fun areAllConnectionsOutOfService_oneConnectionOos_noDeviceEmergencyCalls_nonNtn_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 1 connection
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
 
             // WHEN all of the connections are OOS
             i1.isInService.value = false
@@ -314,7 +419,27 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() =
+    fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connection
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // WHEN all of the connections are OOS
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionOos_ntn_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
@@ -416,6 +541,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index ceaae9e..43b9568 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -75,6 +75,7 @@
                 deviceProvisioningInteractor,
                 wifiInteractor,
                 testScope.backgroundScope,
+                FakeLogBuffer.Factory.create(),
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index dc069fc..8eef930 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -106,6 +106,7 @@
                     android.net.platform.flags.Flags.class,
                     android.os.Flags.class,
                     android.service.controls.flags.Flags.class,
+                    com.android.internal.telephony.flags.Flags.class,
                     com.android.server.notification.Flags.class,
                     com.android.systemui.Flags.class);
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
index d3ceb15..f5d02f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -38,4 +38,8 @@
                 )
             )
     }
+
+    fun setBaseUserRestriction() {
+        _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
new file mode 100644
index 0000000..8ad6087
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.qrcodescanner
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index c4bf8ff..f50443e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -31,7 +31,11 @@
 
     private val mutableInputs = mutableListOf<Input>()
 
-    override fun handle(expandable: Expandable?, intent: Intent) {
+    override fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        handleDismissShadeShowOverLockScreenWhenLocked: Boolean
+    ) {
         mutableInputs.add(Input.Intent(expandable, intent))
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
new file mode 100644
index 0000000..ccfb609
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.base.actions
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
new file mode 100644
index 0000000..146c1ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.base.analytics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
new file mode 100644
index 0000000..9ad49f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.base.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
new file mode 100644
index 0000000..dcfcce7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.qr
+
+import android.content.res.mainResources
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule
+import com.android.systemui.qrcodescanner.qrCodeScannerController
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.qsQRCodeScannerTileConfig by
+    Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) }
+
+val Kosmos.qrCodeScannerTileDataInteractor by
+    Kosmos.Fixture {
+        QRCodeScannerTileDataInteractor(
+            backgroundCoroutineContext,
+            applicationCoroutineScope,
+            qrCodeScannerController
+        )
+    }
+
+val Kosmos.qrCodeScannerTileUserActionInteractor by
+    Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) }
+
+val Kosmos.qrCodeScannerTileMapper by
+    Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) }
+
+val Kosmos.qsQRCodeScannerViewModel by
+    Kosmos.Fixture {
+        QSTileViewModelImpl(
+            qsQRCodeScannerTileConfig,
+            { qrCodeScannerTileUserActionInteractor },
+            { qrCodeScannerTileDataInteractor },
+            { qrCodeScannerTileMapper },
+            fakeDisabledByPolicyInteractor,
+            fakeUserRepository,
+            fakeFalsingManager,
+            qsTileAnalytics,
+            qsTileLogger,
+            systemClock,
+            testDispatcher,
+            testScope.backgroundScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index cce038f..8229575 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -93,6 +94,8 @@
     private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
     override val defaultMobileIconGroup = _defaultMobileIconGroup
 
+    override val deviceServiceState = MutableStateFlow<ServiceStateModel?>(null)
+
     override val isAnySimSecure = MutableStateFlow(false)
     override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index de6c87c2..3a4bf8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -81,6 +81,8 @@
 
     override val isForceHidden = MutableStateFlow(false)
 
+    override val isDeviceInEmergencyCallsOnlyMode = MutableStateFlow(false)
+
     /** Always returns a new fake interactor */
     override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor {
         return FakeMobileIconInteractor(tableLogBuffer).also {
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 340bc32..caa877c 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -81,9 +81,6 @@
       "name": "CtsPermissionTestCases",
       "options": [
         {
-          "include-filter": "android.permissionmultidevice.cts.DeviceAwarePermissionGrantTest"
-        },
-        {
           "include-filter": "android.permission.cts.DevicePermissionsTest"
         },
         {
@@ -93,6 +90,14 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsPermissionMultiDeviceTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a508ebf..8c1bb3b 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1783,7 +1783,13 @@
                 String gidStr = parser.getAttributeValue(null, "gid");
                 if (gidStr != null) {
                     int gid = Process.getGidForName(gidStr);
-                    perm.gids = appendInt(perm.gids, gid);
+                    if (gid != -1) {
+                        perm.gids = appendInt(perm.gids, gid);
+                    } else {
+                        Slog.w(TAG, "<group> with unknown gid \""
+                                + gidStr + " for permission " + name + " in "
+                                + parser.getPositionDescription());
+                    }
                 } else {
                     Slog.w(TAG, "<group> without gid at "
                             + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 04e85c7..a3b6d80 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -134,6 +134,13 @@
         },
         {
             "name": "CtsSuspendAppsTestCases"
+        },
+        {
+            "name": "CtsWindowManagerBackgroundActivityTestCases",
+            "file_patterns": [
+                "Background.*\\.java",
+                "Activity.*\\.java"
+            ]
         }
     ],
     "presubmit-large": [
@@ -189,18 +196,16 @@
             "name": "SelinuxFrameworksTests"
         },
         {
-            "name": "CtsWindowManagerBackgroundActivityTestCases",
-            "file_patterns": [
-                "Background.*\\.java",
-                "Activity.*\\.java"
-            ]
-        },
-        {
             "name": "WmTests",
             "file_patterns": [
                 "Background.*\\.java",
                 "Activity.*\\.java"
+            ],
+            "options": [
+                {
+                    "include-filter": "com.android.server.wm.BackgroundActivityStart*"
+                }
             ]
         }
-    ]
+   ]
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6779f7a..a5449a0 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -37,6 +37,7 @@
 import static android.system.OsConstants.EAGAIN;
 
 import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxInputSelector;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
@@ -2065,11 +2066,15 @@
             }
         }
 
-        return app.info.seInfo
-                + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + extraInfo;
+        if (selinuxSdkSandboxInputSelector()) {
+            return app.info.seInfo + extraInfo + TextUtils.emptyIfNull(app.info.seInfoUser);
+        } else {
+            return app.info.seInfo
+                    + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser)
+                    + extraInfo;
+        }
     }
 
-
     @GuardedBy("mService")
     boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
             int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7dcd952..5793758 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -264,11 +264,11 @@
             Uri settingUri = Settings.Global.getUriFor(globalSetting);
             String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
             if (settingUri == null) {
-                log("setting uri is null for globalSetting " + globalSetting);
+                logErr("setting uri is null for globalSetting " + globalSetting);
                 continue;
             }
             if (propName == null) {
-                log("invalid prop name for globalSetting " + globalSetting);
+                logErr("invalid prop name for globalSetting " + globalSetting);
                 continue;
             }
 
@@ -296,7 +296,7 @@
                         for (String key : properties.getKeyset()) {
                             String propertyName = makePropertyName(scope, key);
                             if (propertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -308,7 +308,7 @@
                             // sys prop slot can be removed.
                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
                             if (aconfigPropertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -326,7 +326,7 @@
                         for (String key : properties.getKeyset()) {
                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
                             if (aconfigPropertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -356,7 +356,7 @@
 
                   if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
                       || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
-                    log("unable to construct system property for " + actualNamespace
+                    logErr("unable to construct system property for " + actualNamespace
                         + "/" + flagName);
                     continue;
                   }
@@ -394,9 +394,9 @@
         try{
             client.connect(new LocalSocketAddress(
                 "aconfigd", LocalSocketAddress.Namespace.RESERVED));
-            log("connected to aconfigd socket");
+            Slog.d(TAG, "connected to aconfigd socket");
         } catch (IOException ioe) {
-            log("failed to connect to aconfigd socket", ioe);
+            logErr("failed to connect to aconfigd socket", ioe);
             return null;
         }
 
@@ -406,7 +406,7 @@
             inputStream = new DataInputStream(client.getInputStream());
             outputStream = new DataOutputStream(client.getOutputStream());
         } catch (IOException ioe) {
-            log("failed to get local socket iostreams", ioe);
+            logErr("failed to get local socket iostreams", ioe);
             return null;
         }
 
@@ -415,9 +415,9 @@
             byte[] requests_bytes = requests.getBytes();
             outputStream.writeInt(requests_bytes.length);
             outputStream.write(requests_bytes, 0, requests_bytes.length);
-            log("flag override requests sent to aconfigd");
+            Slog.d(TAG, "flag override requests sent to aconfigd");
         } catch (IOException ioe) {
-            log("failed to send requests to aconfigd", ioe);
+            logErr("failed to send requests to aconfigd", ioe);
             return null;
         }
 
@@ -425,10 +425,10 @@
         try {
             int num_bytes = inputStream.readInt();
             ProtoInputStream returns = new ProtoInputStream(inputStream);
-            log("received " + num_bytes + " bytes back from aconfigd");
+            Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
             return returns;
         } catch (IOException ioe) {
-            log("failed to read requests return from aconfigd", ioe);
+            logErr("failed to read requests return from aconfigd", ioe);
             return null;
         }
     }
@@ -461,18 +461,18 @@
               long msgsToken = proto.start(StorageReturnMessages.MSGS);
               switch (proto.nextField()) {
                 case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE:
-                  log("successfully handled override requests");
+                  Slog.d(TAG, "successfully handled override requests");
                   long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
                   proto.end(msgToken);
                   break;
                 case (int) StorageReturnMessage.ERROR_MESSAGE:
                   String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
-                  log("override request failed: " + errmsg);
+                  Slog.d(TAG, "override request failed: " + errmsg);
                   break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                   break;
                 default:
-                  log("invalid message type, expecting only flag override return or error message");
+                  logErr("invalid message type, expecting only flag override return or error message");
                   break;
               }
               proto.end(msgsToken);
@@ -480,7 +480,7 @@
             case ProtoInputStream.NO_MORE_FIELDS:
               return;
             default:
-              log("invalid message type, expect storage return message");
+              logErr("invalid message type, expect storage return message");
               break;
           }
         }
@@ -501,14 +501,14 @@
 
             int idx = flagName.indexOf(":");
             if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
-                log("invalid local flag override: " + flagName);
+                logErr("invalid local flag override: " + flagName);
                 continue;
             }
             String actualNamespace = flagName.substring(0, idx);
             String fullFlagName = flagName.substring(idx+1);
             idx = fullFlagName.lastIndexOf(".");
             if (idx == -1) {
-              log("invalid flag name: " + fullFlagName);
+              logErr("invalid flag name: " + fullFlagName);
               continue;
             }
             String packageName = fullFlagName.substring(0, idx);
@@ -528,7 +528,7 @@
         try {
           parseAndLogAconfigdReturn(returns);
         } catch (IOException ioe) {
-            log("failed to parse aconfigd return", ioe);
+            logErr("failed to parse aconfigd return", ioe);
         }
     }
 
@@ -572,7 +572,7 @@
         for (String property_name : property_names) {
             String[] segments = property_name.split("\\.");
             if (segments.length < 3) {
-                log("failed to extract category name from property " + property_name);
+                logErr("failed to extract category name from property " + property_name);
                 continue;
             }
             categories.add(segments[2]);
@@ -617,7 +617,7 @@
                 String stagedValue = flagValuesToStage.get(fullFlagName);
                 int idx = fullFlagName.lastIndexOf(".");
                 if (idx == -1) {
-                    log("invalid flag name: " + fullFlagName);
+                    logErr("invalid flag name: " + fullFlagName);
                     continue;
                 }
                 String packageName = fullFlagName.substring(0, idx);
@@ -638,7 +638,7 @@
         try {
           parseAndLogAconfigdReturn(returns);
         } catch (IOException ioe) {
-            log("failed to parse aconfigd return", ioe);
+            logErr("failed to parse aconfigd return", ioe);
         }
     }
 
@@ -680,7 +680,7 @@
       for (String flagName : properties.getKeyset()) {
         int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
         if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
-          log("invalid staged flag: " + flagName);
+          logErr("invalid staged flag: " + flagName);
           continue;
         }
         String actualNamespace = flagName.substring(0, idx);
@@ -731,7 +731,7 @@
             }
             value = "";
         } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
-            log("key=" + key + " value=" + value + " exceeds system property max length.");
+            logErr("key=" + key + " value=" + value + " exceeds system property max length.");
             return;
         }
 
@@ -741,11 +741,11 @@
             // Failure to set a property can be caused by SELinux denial. This usually indicates
             // that the property wasn't allowlisted in sepolicy.
             // No need to report it on all user devices, only on debug builds.
-            log("Unable to set property " + key + " value '" + value + "'", e);
+            logErr("Unable to set property " + key + " value '" + value + "'", e);
         }
     }
 
-    private static void log(String msg, Exception e) {
+    private static void logErr(String msg, Exception e) {
         if (Build.IS_DEBUGGABLE) {
             Slog.wtf(TAG, msg, e);
         } else {
@@ -753,7 +753,7 @@
         }
     }
 
-    private static void log(String msg) {
+    private static void logErr(String msg) {
         if (Build.IS_DEBUGGABLE) {
             Slog.wtf(TAG, msg);
         } else {
@@ -771,7 +771,7 @@
 
             br.close();
         } catch (IOException ioe) {
-            log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+            logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
         }
         return content;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 05c4aa6..cca73b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1650,7 +1650,7 @@
                 // allocation. This action is used by the TV to get the active source from the CEC
                 // network. If the TV sent a source changing CEC message, this action does not have
                 // to continue anymore.
-                if (isTvDevice()) {
+                if (isTvDeviceEnabled()) {
                     tv().removeAction(RequestActiveSourceAction.class);
                 }
                 sendCecCommandWithRetries(command, callback);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 46c5772..f8dda60 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -282,6 +282,28 @@
     @NonNull
     private final String[] mNonPreemptibleInputMethods;
 
+    /**
+     * See {@link #shouldEnableExperimentalConcurrentMultiUserMode(Context)} about when set to be
+     * {@code true}.
+     */
+    private final boolean mExperimentalConcurrentMultiUserModeEnabled;
+
+    /**
+     * Returns {@code true} if experimental concurrent multi-user mode is enabled.
+     *
+     * <p>Currently not compatible with profiles (e.g. work profile).</p>
+     *
+     * @param context {@link Context} to be used to query
+     *                {@link PackageManager#FEATURE_AUTOMOTIVE}
+     * @return {@code true} if experimental concurrent multi-user mode is enabled.
+     */
+    static boolean shouldEnableExperimentalConcurrentMultiUserMode(@NonNull Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && UserManager.isVisibleBackgroundUsersEnabled()
+                && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled)
+                && Flags.concurrentInputMethods();
+    }
+
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
@@ -1191,8 +1213,10 @@
     public static final class Lifecycle extends SystemService {
         private final InputMethodManagerService mService;
 
+
         public Lifecycle(Context context) {
-            this(context, new InputMethodManagerService(context));
+            this(context, new InputMethodManagerService(context,
+                            shouldEnableExperimentalConcurrentMultiUserMode(context)));
         }
 
         public Lifecycle(
@@ -1291,17 +1315,21 @@
         mHandler.post(task);
     }
 
-    public InputMethodManagerService(Context context) {
-        this(context, null, null, null);
+    public InputMethodManagerService(Context context,
+            boolean experimentalConcurrentMultiUserModeEnabled) {
+        this(context, experimentalConcurrentMultiUserModeEnabled, null, null, null);
     }
 
     @VisibleForTesting
     InputMethodManagerService(
             Context context,
+            boolean experimentalConcurrentMultiUserModeEnabled,
             @Nullable ServiceThread serviceThreadForTesting,
             @Nullable ServiceThread packageMonitorThreadForTesting,
             @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
         synchronized (ImfLock.class) {
+            mExperimentalConcurrentMultiUserModeEnabled =
+                    experimentalConcurrentMultiUserModeEnabled;
             mContext = context;
             mRes = context.getResources();
             SecureSettingsWrapper.onStart(mContext);
@@ -1630,8 +1658,9 @@
 
     /**
      * Returns true iff the caller is identified to be the current input method with the token.
-     * @param token The window token given to the input method when it was started.
-     * @return true if and only if non-null valid token is specified.
+     *
+     * @param token the window token given to the input method when it was started
+     * @return true if and only if non-null valid token is specified
      */
     @GuardedBy("ImfLock.class")
     private boolean calledWithValidTokenLocked(@NonNull IBinder token) {
@@ -1787,10 +1816,11 @@
     /**
      * Gets enabled subtypes of the specified {@link InputMethodInfo}.
      *
-     * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}.
+     * @param imiId                           if null, returns enabled subtypes for the current
+     *                                        {@link InputMethodInfo}
      * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled
-     *                                         subtypes.
-     * @param userId the user ID to be queried about.
+     *                                        subtypes
+     * @param userId                          the user ID to be queried about
      */
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
@@ -1834,10 +1864,11 @@
      * <p>As a general principle, IPCs from the application process that take
      * {@link IInputMethodClient} will be rejected without this step.</p>
      *
-     * @param client {@link android.os.Binder} proxy that is associated with the singleton instance
-     *               of {@link android.view.inputmethod.InputMethodManager} that runs on the client
-     *               process
-     * @param inputConnection communication channel for the fallback {@link InputConnection}
+     * @param client                {@link android.os.Binder} proxy that is associated with the
+     *                              singleton instance of
+     *                              {@link android.view.inputmethod.InputMethodManager} that runs
+     *                              on the client process
+     * @param inputConnection       communication channel for the fallback {@link InputConnection}
      * @param selfReportedDisplayId self-reported display ID to which the client is associated.
      *                              Whether the client is still allowed to access to this display
      *                              or not needs to be evaluated every time the client interacts
@@ -1862,10 +1893,10 @@
         }
     }
 
-    // TODO(b/325515685): Move this method to InputMethodBindingController
     /**
      * Hide the IME if the removed user is the current user.
      */
+    // TODO(b/325515685): Move this method to InputMethodBindingController
     @GuardedBy("ImfLock.class")
     private void onClientRemoved(ClientState client) {
         clearClientSessionLocked(client);
@@ -2222,11 +2253,13 @@
 
     /**
      * Update the current deviceId and return the relevant imeId for this device.
-     *   1. If the device changes to virtual and its custom IME is not available, then disable IME.
-     *   2. If the device changes to virtual with valid custom IME, then return the custom IME. If
-     *      the old device was default, then store the current imeId so it can be restored.
-     *   3. If the device changes to default, restore the default device IME.
-     *   4. Otherwise keep the current imeId.
+     *
+     * <p>1. If the device changes to virtual and its custom IME is not available, then disable
+     * IME.</p>
+     * <p>2. If the device changes to virtual with valid custom IME, then return the custom IME. If
+     * the old device was default, then store the current imeId so it can be restored.</p>
+     * <p>3. If the device changes to default, restore the default device IME.</p>
+     * <p>4. Otherwise keep the current imeId.</p>
      */
     @GuardedBy("ImfLock.class")
     private String computeCurrentDeviceMethodIdLocked(String currentMethodId) {
@@ -2370,12 +2403,12 @@
     /**
      * Find the display where the IME should be shown.
      *
-     * @param displayId the ID of the display where the IME client target is.
-     * @param checker instance of {@link ImeDisplayValidator} which is used for
-     *                checking display config to adjust the final target display.
-     * @return The ID of the display where the IME should be shown or
-     *         {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
-     *         {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
+     * @param displayId the ID of the display where the IME client target is
+     * @param checker   instance of {@link ImeDisplayValidator} which is used for
+     *                  checking display config to adjust the final target display
+     * @return the ID of the display where the IME should be shown or
+     * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
+     * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}
      */
     static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
@@ -4149,7 +4182,6 @@
      * This is kept due to {@code @UnsupportedAppUsage} in
      * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
      * {@link InputMethodService#onCreate()}.
-     *
      * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)}
      *
      * @deprecated TODO(b/113914148): Check if we can remove this
@@ -4308,7 +4340,8 @@
     /**
      * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow}
      * will be removed.
-     * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
+     *
+     * @param timeout to set in milliseconds. To reset to default, use a value <= zero
      */
     @BinderThread
     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@@ -5200,10 +5233,10 @@
     /**
      * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}.
      *
-     * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not
-     *           recognized by the system.
-     * @param enabled {@code true} if {@code id} needs to be enabled.
-     * @return {@code true} if the IME was previously enabled. {@code false} otherwise.
+     * @param id      ID of the IME is to be manipulated. It is OK to pass IME ID that is currently
+     *                not recognized by the system
+     * @param enabled {@code true} if {@code id} needs to be enabled
+     * @return {@code true} if the IME was previously enabled
      */
     @GuardedBy("ImfLock.class")
     private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
@@ -5310,8 +5343,8 @@
     /**
      * Gets the current subtype of this input method.
      *
-     * @param userId User ID to be queried about.
-     * @return The current {@link InputMethodSubtype} for the specified user.
+     * @param userId User ID to be queried about
+     * @return the current {@link InputMethodSubtype} for the specified user
      */
     @Nullable
     @Override
@@ -5385,7 +5418,8 @@
 
     /**
      * Returns the default {@link InputMethodInfo} for the specific userId.
-     * @param userId user ID to query.
+     *
+     * @param userId user ID to query
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
@@ -5419,11 +5453,11 @@
      * Filter the access to the input method by rules of the package visibility. Return {@code true}
      * if the given input method is the currently selected one or visible to the caller.
      *
-     * @param targetPkgName The package name of input method to check.
-     * @param callingUid The caller that is going to access the input method.
-     * @param userId The user ID where the input method resides.
-     * @param settings The input method settings under the given user ID.
-     * @return {@code true} if caller is able to access the input method.
+     * @param targetPkgName the package name of input method to check
+     * @param callingUid    the caller that is going to access the input method
+     * @param userId        the user ID where the input method resides
+     * @param settings      the input method settings under the given user ID
+     * @return {@code true} if caller is able to access the input method
      */
     private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid,
             @UserIdInt int userId, @NonNull InputMethodSettings settings) {
@@ -5945,6 +5979,8 @@
             mVisibilityStateComputer.dump(pw, "  ");
             p.println("  mInFullscreenMode=" + mInFullscreenMode);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
+            p.println("  mExperimentalConcurrentMultiUserModeEnabled="
+                    + mExperimentalConcurrentMultiUserModeEnabled);
             p.println("  ENABLE_HIDE_IME_CAPTION_BAR="
                     + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
             p.println("  mSettingsObserver=" + mSettingsObserver);
@@ -6177,8 +6213,9 @@
 
     /**
      * Handles {@code adb shell ime list}.
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
-     * @return Exit code of the command.
+     *
+     * @param shellCommand {@link ShellCommand} object that is handling this command
+     * @return exit code of the command
      */
     @BinderThread
     @ShellCommandResult
@@ -6236,9 +6273,9 @@
     /**
      * Handles {@code adb shell ime enable} and {@code adb shell ime disable}.
      *
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
-     * @param enabled      {@code true} if the command was {@code adb shell ime enable}.
-     * @return Exit code of the command.
+     * @param shellCommand {@link ShellCommand} object that is handling this command
+     * @param enabled      {@code true} if the command was {@code adb shell ime enable}
+     * @return exit code of the command
      */
     @BinderThread
     @ShellCommandResult
@@ -6273,8 +6310,8 @@
      * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the
      * main arguments.</p>
      *
-     * @param shellCommand {@link ShellCommand} from which options should be obtained.
-     * @return User ID to be resolved. {@link UserHandle#CURRENT} if not specified.
+     * @param shellCommand {@link ShellCommand} from which options should be obtained
+     * @return user ID to be resolved. {@link UserHandle#CURRENT} if not specified
      */
     @BinderThread
     @UserIdInt
@@ -6296,12 +6333,12 @@
     /**
      * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}.
      *
-     * @param userId user ID specified to the command.  Pseudo user IDs are not supported.
-     * @param imeId IME ID specified to the command.
-     * @param enabled {@code true} for {@code adb shell ime enable}. {@code false} otherwise.
-     * @param out {@link PrintWriter} to output standard messages.
-     * @param error {@link PrintWriter} to output error messages.
-     * @return {@code false} if it fails to enable the IME.  {@code false} otherwise.
+     * @param userId  user ID specified to the command (pseudo user IDs are not supported)
+     * @param imeId   IME ID specified to the command
+     * @param enabled {@code true} for {@code adb shell ime enable}
+     * @param out     {@link PrintWriter} to output standard messages
+     * @param error   {@link PrintWriter} to output error messages
+     * @return {@code false} if it fails to enable the IME
      */
     @BinderThread
     @GuardedBy("ImfLock.class")
@@ -6359,7 +6396,7 @@
     /**
      * Handles {@code adb shell ime set}.
      *
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
+     * @param shellCommand {@link ShellCommand} object that is handling this command
      * @return Exit code of the command.
      */
     @BinderThread
@@ -6402,7 +6439,8 @@
 
     /**
      * Handles {@code adb shell ime reset-ime}.
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
+     *
+     * @param shellCommand {@link ShellCommand} object that is handling this command
      * @return Exit code of the command.
      */
     @BinderThread
@@ -6483,7 +6521,8 @@
 
     /**
      * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
+     *
+     * @param shellCommand {@link ShellCommand} object that is handling this command
      * @return Exit code of the command.
      */
     @BinderThread
@@ -6528,9 +6567,9 @@
 
     /**
      * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()}
-     * and *not* pseudo ids like {@link UserHandle#USER_ALL etc}.
-     * @return {@code true} if userId has debugging privileges.
-     * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}.
+     *               and *not* pseudo ids like {@link UserHandle#USER_ALL etc}
+     * @return {@code true} if userId has debugging privileges
+     * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}
      */
     private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) {
         if (mUserManagerInternal.hasUserRestriction(
@@ -6551,8 +6590,8 @@
     /**
      * Creates an IME request tracking token for the current focused client.
      *
-     * @param show whether this is a show or a hide request.
-     * @param reason the reason why the IME request was created.
+     * @param show   whether this is a show or a hide request
+     * @param reason the reason why the IME request was created
      */
     @NonNull
     private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
index f06643df..bab21e8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
@@ -110,7 +110,7 @@
      * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data
      */
     @AnyThread
-    static boolean equals(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) {
+    static boolean areSame(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) {
         if (map1 == map2) {
             return true;
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index a3c5d2d..69f07d5 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1057,6 +1057,7 @@
         return -1;
     }
 
+    @NonNull
     private PlaybackInfo getVolumeAttributes() {
         int volumeType;
         AudioAttributes attributes;
@@ -1850,6 +1851,7 @@
             return mFlags;
         }
 
+        @NonNull
         @Override
         public PlaybackInfo getVolumeAttributes() {
             return MediaSessionRecord.this.getVolumeAttributes();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4ff345f..ebdca5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1924,16 +1924,20 @@
     private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
         if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
-            // TODO (b/308121702) It may be brittle to rely on user states to check profile state
-            int state;
-            synchronized (mUserStates) {
-                state = mUserStates.get(userId, UserState.STATE_NONE);
-            }
-            if (state != UserState.STATE_NONE) {
-                Slog.i(LOG_TAG,
-                        "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
-                                + " is still alive.");
-                return;
+            if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles()
+                    || getUserInfo(userId).isManagedProfile()) {
+                // TODO (b/308121702) It may be brittle to rely on user states to check managed
+                //  profile state
+                int state;
+                synchronized (mUserStates) {
+                    state = mUserStates.get(userId, UserState.STATE_NONE);
+                }
+                if (state != UserState.STATE_NONE) {
+                    Slog.i(LOG_TAG,
+                            "showConfirmCredentialToDisableQuietMode() called too early, managed "
+                                    + "user " + userId + " is still alive.");
+                    return;
+                }
             }
         }
         // otherwise, we show a profile challenge to trigger decryption of the user
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0066269..76e7f53 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6530,8 +6530,8 @@
             // and the token could be null.
             return;
         }
-        if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
-            r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+        if (r.mDisplayContent.mActivityRefresher != null) {
+            r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
         }
     }
 
@@ -9996,7 +9996,7 @@
             } else {
                 scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
             }
-            notifyDisplayCompatPolicyAboutConfigurationChange(
+            notifyActivityRefresherAboutConfigurationChange(
                     mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
             return true;
         }
@@ -10063,18 +10063,18 @@
         } else {
             scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
         }
-        notifyDisplayCompatPolicyAboutConfigurationChange(
+        notifyActivityRefresherAboutConfigurationChange(
                 mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
         return true;
     }
 
-    private void notifyDisplayCompatPolicyAboutConfigurationChange(
+    private void notifyActivityRefresherAboutConfigurationChange(
             Configuration newConfig, Configuration lastReportedConfig) {
-        if (mDisplayContent.mDisplayRotationCompatPolicy == null
+        if (mDisplayContent.mActivityRefresher == null
                 || !shouldBeResumed(/* activeActivity */ null)) {
             return;
         }
-        mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+        mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
                 this, newConfig, lastReportedConfig);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
new file mode 100644
index 0000000..23a9708
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 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 static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
+ *
+ * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
+ * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
+ * through either stop or pause and then resume, based on the global config and per-app override.
+ */
+class ActivityRefresher {
+    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+    // client process may not always report the event back to the server, such as process is
+    // crashed or got killed.
+    private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L;
+
+    @NonNull private final WindowManagerService mWmService;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();
+
+    ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
+        mWmService = wmService;
+        mHandler = handler;
+    }
+
+    void addEvaluator(@NonNull Evaluator evaluator) {
+        mEvaluators.add(evaluator);
+    }
+
+    void removeEvaluator(@NonNull Evaluator evaluator) {
+        mEvaluators.remove(evaluator);
+    }
+
+    /**
+     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+     * camera preview and can lead to sideways or stretching issues persisting even after force
+     * rotation.
+     */
+    void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+        if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+            return;
+        }
+
+        final boolean cycleThroughStop =
+                mWmService.mLetterboxConfiguration
+                        .isCameraCompatRefreshCycleThroughStopEnabled()
+                        && !activity.mLetterboxUiController
+                        .shouldRefreshActivityViaPauseForCameraCompat();
+
+        activity.mLetterboxUiController.setIsRefreshRequested(true);
+        ProtoLog.v(WM_DEBUG_STATES,
+                "Refreshing activity for freeform camera compatibility treatment, "
+                        + "activityRecord=%s", activity);
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
+                activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+                activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+        try {
+            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+            mHandler.postDelayed(() -> {
+                synchronized (mWmService.mGlobalLock) {
+                    onActivityRefreshed(activity);
+                }
+            }, REFRESH_CALLBACK_TIMEOUT_MS);
+        } catch (RemoteException e) {
+            activity.mLetterboxUiController.setIsRefreshRequested(false);
+        }
+    }
+
+    boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
+        return activity.mLetterboxUiController.isRefreshRequested();
+    }
+
+    void onActivityRefreshed(@NonNull ActivityRecord activity) {
+        // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
+        //  state?
+        activity.mLetterboxUiController.setIsRefreshRequested(false);
+    }
+
+    private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+        return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
+                ((Evaluator) evaluator)
+                        .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
+    }
+
+    /**
+     * Interface for classes that would like to refresh the recently updated activity, based on the
+     * configuration change.
+     */
+    interface Evaluator {
+        boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+                @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5079ec1..e49cb38 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -478,6 +478,8 @@
     final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     @Nullable
     final CameraStateMonitor mCameraStateMonitor;
+    @Nullable
+    final ActivityRefresher mActivityRefresher;
 
     DisplayFrames mDisplayFrames;
     final DisplayUpdater mDisplayUpdater;
@@ -1233,13 +1235,15 @@
                 mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
         if (shouldCreateDisplayRotationCompatPolicy) {
             mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
+            mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
             mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
-                    this, mWmService.mH, mCameraStateMonitor);
+                    this, mCameraStateMonitor, mActivityRefresher);
 
             mCameraStateMonitor.startListeningToCameraState();
         } else {
             // These are to satisfy the `final` check.
             mCameraStateMonitor = null;
+            mActivityRefresher = null;
             mDisplayRotationCompatPolicy = null;
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index eacf9a3..e0cc064 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -18,8 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -32,19 +30,14 @@
 import static android.view.Display.TYPE_INTERNAL;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.app.servertransaction.RefreshCallbackItem;
-import android.app.servertransaction.ResumeActivityItem;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.RemoteException;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -64,48 +57,38 @@
  * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
  */
  // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener,
+        ActivityRefresher.Evaluator {
 
-    // Delay for updating display rotation after Camera connection is closed. Needed to avoid
-    // rotation flickering when an app is flipping between front and rear cameras or when size
-    // compat mode is restarted.
-    // TODO(b/263114289): Consider associating this delay with a specific activity so that if
-    // the new non-camera activity started on top of the camer one we can rotate faster.
-    private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
-    // Delay for updating display rotation after Camera connection is opened. This delay is
-    // selected to be long enough to avoid conflicts with transitions on the app's side.
-    // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
-    // is flipping between front and rear cameras (in case requested orientation changes at
-    // runtime at the same time) or when size compat mode is restarted.
-    private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
-            CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
-    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
-    // client process may not always report the event back to the server, such as process is
-    // crashed or got killed.
-    private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
-
+    @NonNull
     private final DisplayContent mDisplayContent;
+    @NonNull
     private final WindowManagerService mWmService;
+    @NonNull
     private final CameraStateMonitor mCameraStateMonitor;
-    private final Handler mHandler;
+    @NonNull
+    private final ActivityRefresher mActivityRefresher;
 
     @ScreenOrientation
     private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
 
-    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler,
-            @NonNull CameraStateMonitor cameraStateMonitor) {
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent,
+            @NonNull CameraStateMonitor cameraStateMonitor,
+            @NonNull ActivityRefresher activityRefresher) {
         // This constructor is called from DisplayContent constructor. Don't use any fields in
         // DisplayContent here since they aren't guaranteed to be set.
-        mHandler = handler;
         mDisplayContent = displayContent;
         mWmService = displayContent.mWmService;
         mCameraStateMonitor = cameraStateMonitor;
         mCameraStateMonitor.addCameraStateListener(this);
+        mActivityRefresher = activityRefresher;
+        mActivityRefresher.addEvaluator(this);
     }
 
     /** Releases camera state listener. */
     void dispose() {
         mCameraStateMonitor.removeCameraStateListener(this);
+        mActivityRefresher.removeEvaluator(this);
     }
 
     /**
@@ -169,47 +152,6 @@
     }
 
     /**
-     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
-     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
-     * camera preview and can lead to sideways or stretching issues persisting even after force
-     * rotation.
-     */
-    void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
-            Configuration lastReportedConfig) {
-        if (!isTreatmentEnabledForDisplay()
-                || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
-                || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
-            return;
-        }
-        boolean cycleThroughStop =
-                mWmService.mLetterboxConfiguration
-                        .isCameraCompatRefreshCycleThroughStopEnabled()
-                && !activity.mLetterboxUiController
-                        .shouldRefreshActivityViaPauseForCameraCompat();
-        try {
-            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
-            ProtoLog.v(WM_DEBUG_STATES,
-                    "Refreshing activity for camera compatibility treatment, "
-                            + "activityRecord=%s", activity);
-            final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
-                    activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
-            final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
-                    activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
-            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
-                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
-            mHandler.postDelayed(
-                    () -> onActivityRefreshed(activity),
-                    REFRESH_CALLBACK_TIMEOUT_MS);
-        } catch (RemoteException e) {
-            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
-        }
-    }
-
-    void onActivityRefreshed(@NonNull ActivityRecord activity) {
-        activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
-    }
-
-    /**
      * Notifies that animation in {@link ScreenRotationAnimation} has finished.
      *
      * <p>This class uses this signal as a trigger for notifying the user about forced rotation
@@ -276,14 +218,16 @@
 
     // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
     // treatment is enabled
-    private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
-            Configuration lastReportedConfig) {
+    @Override
+    public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
         final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation()
                 != lastReportedConfig.windowConfiguration.getDisplayRotation());
-        return (displayRotationChanged
-                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed())
+        return isTreatmentEnabledForDisplay()
                 && isTreatmentEnabledForActivity(activity)
-                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
+                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && (displayRotationChanged
+                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
     }
 
     /**
@@ -310,7 +254,6 @@
                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
 
-
     /**
      * Whether camera compat treatment is applicable for the given activity.
      *
@@ -429,6 +372,6 @@
                 || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
             return false;
         }
-        return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+        return mActivityRefresher.isActivityRefreshing(topActivity);
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 4400ed2..2288fe9 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -198,7 +198,7 @@
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
                 if (mPendingControlTarget != mControlTarget) {
-                    updateControlForTarget(mPendingControlTarget, true /* force */);
+                    mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 474877c..16d7b4f 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -260,7 +260,7 @@
     // Whether activity "refresh" was requested but not finished in
     // ActivityRecord#activityResumedLocked following the camera compat force rotation in
     // DisplayRotationCompatPolicy.
-    private boolean mIsRefreshAfterRotationRequested;
+    private boolean mIsRefreshRequested;
 
     @NonNull
     private final OptProp mIgnoreRequestedOrientationOptProp;
@@ -571,15 +571,14 @@
     }
 
     /**
-     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
-     * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
      */
-    boolean isRefreshAfterRotationRequested() {
-        return mIsRefreshAfterRotationRequested;
+    boolean isRefreshRequested() {
+        return mIsRefreshRequested;
     }
 
-    void setIsRefreshAfterRotationRequested(boolean isRequested) {
-        mIsRefreshAfterRotationRequested = isRequested;
+    void setIsRefreshRequested(boolean isRequested) {
+        mIsRefreshRequested = isRequested;
     }
 
     boolean isOverrideRespectRequestedOrientationEnabled() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d114337..d733762 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -217,7 +217,7 @@
     <V> void setLocalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
-            @Nullable PolicyValue<V> value,
+            @NonNull PolicyValue<V> value,
             int userId,
             boolean skipEnforcePolicy) {
         Objects.requireNonNull(policyDefinition);
@@ -313,6 +313,7 @@
         }
         updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
         write();
+        applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
@@ -400,7 +401,7 @@
      * else remove the policy from child.
      */
     private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
-            EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) {
+            EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) {
         if (policyDefinition.isInheritable()) {
             Binder.withCleanCallingIdentity(() -> {
                 List<UserInfo> userInfos = mUserManager.getProfiles(userId);
@@ -1742,14 +1743,17 @@
         }
     }
 
-    <V> void reapplyAllPoliciesLocked() {
+    <V> void reapplyAllPoliciesOnBootLocked() {
         for (PolicyKey policy : mGlobalPolicies.keySet()) {
             PolicyState<?> policyState = mGlobalPolicies.get(policy);
             // Policy definition and value will always be of the same type
             PolicyDefinition<V> policyDefinition =
                     (PolicyDefinition<V>) policyState.getPolicyDefinition();
-            PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
-            enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+            if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+                PolicyValue<V> policyValue =
+                        (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+                enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+            }
         }
         for (int i = 0; i < mLocalPolicies.size(); i++) {
             int userId = mLocalPolicies.keyAt(i);
@@ -1758,10 +1762,11 @@
                 // Policy definition and value will always be of the same type
                 PolicyDefinition<V> policyDefinition =
                         (PolicyDefinition<V>) policyState.getPolicyDefinition();
-                PolicyValue<V> policyValue =
-                        (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
-                enforcePolicy(policyDefinition, policyValue, userId);
-
+                if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+                    PolicyValue<V> policyValue =
+                            (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+                    enforcePolicy(policyDefinition, policyValue, userId);
+                }
             }
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b93d21..85d2a0d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3351,7 +3351,7 @@
                 break;
             case SystemService.PHASE_SYSTEM_SERVICES_READY:
                 synchronized (getLockObject()) {
-                    mDevicePolicyEngine.reapplyAllPoliciesLocked();
+                    mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked();
                 }
                 break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
@@ -11443,7 +11443,7 @@
             }
             setBackwardsCompatibleAppRestrictions(
                     caller, packageName, restrictions, caller.getUserHandle());
-        } else if (Flags.dmrhCanSetAppRestriction()) {
+        } else if (Flags.dmrhSetAppRestrictions()) {
             final boolean isRoleHolder;
             if (who != null) {
                 // DO or PO
@@ -11484,10 +11484,6 @@
                             new BundlePolicyValue(restrictions),
                             affectedUserId);
                 }
-                Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
-                changeIntent.setPackage(packageName);
-                changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
             } else {
                 mInjector.binderWithCleanCallingIdentity(() -> {
                     mUserManager.setApplicationRestrictions(packageName, restrictions,
@@ -12845,7 +12841,7 @@
                 return Bundle.EMPTY;
             }
             return policies.get(enforcingAdmin).getValue();
-        } else if (Flags.dmrhCanSetAppRestriction()) {
+        } else if (Flags.dmrhSetAppRestrictions()) {
             final boolean isRoleHolder;
             if (who != null) {
                 // Caller is DO or PO. They cannot call this on parent
@@ -15770,8 +15766,13 @@
                             PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
                             userId);
             List<Bundle> restrictions = new ArrayList<>();
-            for (EnforcingAdmin admin : policies.keySet()) {
-                restrictions.add(policies.get(admin).getValue());
+            for (PolicyValue<Bundle> policyValue: policies.values()) {
+                Bundle value = policyValue.getValue();
+                // Probably not necessary since setApplicationRestrictions only sets non-empty
+                // Bundle, but just in case.
+                if (value != null && !value.isEmpty()) {
+                    restrictions.add(value);
+                }
             }
 
             return mInjector.binderWithCleanCallingIdentity(() -> {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 1000bfa..cbd2847 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -52,7 +52,18 @@
     EnterpriseSpecificIdCalculator(Context context) {
         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
         Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
-        mImei = telephonyService.getImei(0);
+
+        String imei;
+        try {
+            imei = telephonyService.getImei(0);
+        } catch (UnsupportedOperationException doesNotSupportGms) {
+            // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
+            // However that runs the risk of changing a device's existing ESID if on these devices
+            // telephonyService.getImei() actually returns non-null even when the device does not
+            // declare FEATURE_TELEPHONY_GSM.
+            imei = null;
+        }
+        mImei = imei;
         String meid;
         try {
             meid = telephonyService.getMeid(0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8d980b5..8bec384 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -51,6 +51,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 final class PolicyDefinition<V> {
@@ -82,6 +83,10 @@
     // them.
     private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4;
 
+    // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the
+    // callback in other cases such as device reboots.
+    private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5;
+
     private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
             List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
 
@@ -231,11 +236,11 @@
                     // Don't need to take in a resolution mechanism since its never used, but might
                     // need some refactoring to not always assume a non-null mechanism.
                     new MostRecent<>(),
-                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
-                    // Application restrictions are now stored and retrieved from DPMS, so no
-                    // enforcing is required, however DPMS calls into UM to set restrictions for
-                    // backwards compatibility.
-                    (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true,
+                    // Only invoke the enforcement callback during policy change and not other state
+                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE
+                            | POLICY_FLAG_NON_COEXISTABLE_POLICY
+                            | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED,
+                    PolicyEnforcerCallbacks::setApplicationRestrictions,
                     new BundlePolicySerializer());
 
     /**
@@ -581,6 +586,10 @@
         return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0;
     }
 
+    boolean shouldSkipEnforcementIfNotChanged() {
+        return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0;
+    }
+
     @Nullable
     PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
         return mResolutionMechanism.resolve(adminsPolicy);
@@ -610,7 +619,7 @@
      * {@link Object#equals} implementation.
      */
     private PolicyDefinition(
-            PolicyKey key,
+            @NonNull  PolicyKey key,
             ResolutionMechanism<V> resolutionMechanism,
             QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
@@ -622,11 +631,12 @@
      * {@link Object#equals} and {@link Object#hashCode()} implementation.
      */
     private PolicyDefinition(
-            PolicyKey policyKey,
+            @NonNull  PolicyKey policyKey,
             ResolutionMechanism<V> resolutionMechanism,
             int policyFlags,
             QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
+        Objects.requireNonNull(policyKey);
         mPolicyKey = policyKey;
         mResolutionMechanism = resolutionMechanism;
         mPolicyFlags = policyFlags;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 09eef45..04d277e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -37,11 +37,13 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -172,6 +174,29 @@
         return true;
     }
 
+
+    /**
+     * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing
+     * it to UMS) is required. Only need to send broadcast to the target user here as we rely on
+     * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple
+     * profiles. The broadcast should only be sent when an application restriction is set, so we
+     * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
+     * when the policy is set, and not during system boot or other situations.
+     */
+    static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
+            PolicyKey policyKey) {
+        Binder.withCleanCallingIdentity(() -> {
+            PackagePolicyKey key = (PackagePolicyKey) policyKey;
+            String packageName = key.getPackageName();
+            Objects.requireNonNull(packageName);
+            Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+            changeIntent.setPackage(packageName);
+            changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
+        });
+        return true;
+    }
+
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 2bbd3c0..3b25cb1 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -231,8 +231,10 @@
                         "immstest2",
                         Process.THREAD_PRIORITY_FOREGROUND,
                         true /* allowIo */);
-        mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
-                mPackageMonitorThread, unusedUserId -> mMockInputMethodBindingController);
+        mInputMethodManagerService = new InputMethodManagerService(mContext,
+                InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext),
+                mServiceThread, mPackageMonitorThread,
+                unusedUserId -> mMockInputMethodBindingController);
         spyOn(mInputMethodManagerService);
 
         // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
index 5e3bc56..be70421 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
@@ -44,49 +44,49 @@
     }
 
     @Test
-    public void testEqualsSameObject() {
+    public void testAreSameSameObject() {
         final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
         final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
         final var map = toMap(imi1, imi2);
         assertTrue("Must return true for the same instance",
-                InputMethodMap.equals(map, map));
+                InputMethodMap.areSame(map, map));
     }
 
     @Test
-    public void testEqualsEquivalentObject() {
+    public void testAreSameEquivalentObject() {
         final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
         final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
         assertTrue("Must return true for the equivalent instances",
-                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi2)));
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi2)));
 
         assertTrue("Must return true for the equivalent instances",
-                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi2, imi1)));
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi2, imi1)));
     }
 
     @Test
-    public void testEqualsDifferentKeys() {
+    public void testAreSameDifferentKeys() {
         final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
         final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
         final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3));
         assertFalse("Must return false if keys are different",
-                InputMethodMap.equals(toMap(imi1), toMap(imi1, imi2)));
+                InputMethodMap.areSame(toMap(imi1), toMap(imi1, imi2)));
         assertFalse("Must return false if keys are different",
-                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1)));
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1)));
         assertFalse("Must return false if keys are different",
-                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi3)));
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi3)));
     }
 
     @Test
-    public void testEqualsDifferentValues() {
+    public void testAreSameDifferentValues() {
         final var imi1_without_subtypes =
                 createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
         final var imi1_with_subtypes =
                 createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
         final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
         assertFalse("Must return false if values are different",
-                InputMethodMap.equals(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes)));
+                InputMethodMap.areSame(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes)));
         assertFalse("Must return false if values are different",
-                InputMethodMap.equals(
+                InputMethodMap.areSame(
                         toMap(imi1_without_subtypes, imi2),
                         toMap(imi1_with_subtypes, imi2)));
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
new file mode 100644
index 0000000..12ab3e1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 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 static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ActivityRefresher}.
+ *
+ * <p>Build/Install/Run:
+ *  atest WmTests:ActivityRefresherTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class ActivityRefresherTests extends WindowTestsBase {
+    private Handler mMockHandler;
+    private LetterboxConfiguration mLetterboxConfiguration;
+
+    private ActivityRecord mActivity;
+    private ActivityRefresher mActivityRefresher;
+
+    private ActivityRefresher.Evaluator mEvaluatorFalse =
+            (activity, newConfig, lastReportedConfig) -> false;
+
+    private ActivityRefresher.Evaluator mEvaluatorTrue =
+            (activity, newConfig, lastReportedConfig) -> true;
+
+    private final Configuration mNewConfig = new Configuration();
+    private final Configuration mOldConfig = new Configuration();
+
+    @Before
+    public void setUp() throws Exception {
+        mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+        spyOn(mLetterboxConfiguration);
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(true);
+
+        mMockHandler = mock(Handler.class);
+        when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+                invocation -> {
+                    ((Runnable) invocation.getArgument(0)).run();
+                    return null;
+                });
+
+        mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshDisabled() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(false);
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
+        configureActivityAndDisplay();
+        when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+                .thenReturn(false);
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception {
+        configureActivityAndDisplay();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorFalse);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorFalse);
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ true);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled()
+            throws Exception {
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(false);
+        configureActivityAndDisplay();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp()
+            throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    @Test
+    public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
+
+        mActivityRefresher.onActivityRefreshed(mActivity);
+
+        assertActivityRefreshRequested(false);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+        assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested,
+            boolean cycleThroughStop) throws Exception {
+        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+                .setIsRefreshRequested(true);
+
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
+                cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+
+        verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+                .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+                        refreshCallbackItem, resumeActivityItem);
+    }
+
+    private void configureActivityAndDisplay() {
+        mActivity = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setDisplay(mDisplayContent)
+                // Set the component to be that of the test class in order to enable compat changes
+                .setComponent(ComponentName.createRelative(mContext,
+                        ActivityRefresherTests.class.getName()))
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build()
+                .getTopMostActivity();
+
+        spyOn(mActivity.mLetterboxUiController);
+        doReturn(true).when(
+                mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+
+        doReturn(true).when(mActivity).inFreeformWindowingMode();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 262ba8b..c76acd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -91,6 +91,7 @@
     private CameraManager mMockCameraManager;
     private Handler mMockHandler;
     private LetterboxConfiguration mLetterboxConfiguration;
+    private ActivityRefresher mActivityRefresher;
 
     private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
@@ -132,8 +133,9 @@
                 });
         CameraStateMonitor cameraStateMonitor =
                 new CameraStateMonitor(mDisplayContent, mMockHandler);
-        mDisplayRotationCompatPolicy =
-                new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor);
+        mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+        mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent,
+                cameraStateMonitor, mActivityRefresher);
 
         // Do not show the real toast.
         spyOn(mDisplayRotationCompatPolicy);
@@ -606,7 +608,7 @@
     private void assertActivityRefreshRequested(boolean refreshRequested,
                 boolean cycleThroughStop) throws Exception {
         verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
-                .setIsRefreshAfterRotationRequested(true);
+                .setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
                 cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -628,7 +630,7 @@
 
     private void callOnActivityConfigurationChanging(
             ActivityRecord activity, boolean isDisplayRotationChanging) {
-        mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+        mActivityRefresher.onActivityConfigurationChanging(activity,
                 /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
                 /* newConfig */ createConfigurationWithDisplayRotation(
                         isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0e1a1af..c69faed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -353,6 +353,17 @@
     }
 
     @Test
+    public void testControlTargetChangedWhileProviderHasNoWindow() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final InsetsSourceProvider provider = getController().getOrCreateSourceProvider(
+                ID_STATUS_BAR, statusBars());
+        getController().onBarControlTargetChanged(app, null, null, null);
+        assertNull(getController().getControlsForDispatch(app));
+        provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null);
+        assertNotNull(getController().getControlsForDispatch(app));
+    }
+
+    @Test
     public void testTransientVisibilityOfFixedRotationState() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
similarity index 99%
rename from tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 001a09a..be9fb1b 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -34,7 +34,7 @@
 import perfetto.protos.ProtologCommon;
 import perfetto.protos.ProtologConfig;
 
-public class PerfettoDataSourceTest {
+public class ProtologDataSourceTest {
     @Before
     public void before() {
         assumeTrue(android.tracing.Flags.perfettoProtologTracing());