Merge "Update boot image and system server profiles [M24C20P47S0PP]" into udc-dev
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7be00a0..3487e0b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -827,6 +827,12 @@
      */
     public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
 
+    /**
+     * Whether AbiOverride was used when installing this application.
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
             PRIVATE_FLAG_EXT_PROFILEABLE,
@@ -834,6 +840,7 @@
             PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
             PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
             PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
+            PRIVATE_FLAG_EXT_CPU_OVERRIDE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlagsExt {}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 931adb5..ef274a5 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.Application.ActivityLifecycleCallbacks;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -535,7 +536,11 @@
                     return null;
                 }
                 try {
-                    mContext.startIntentSender(intent, null, 0, 0, 0);
+                    ActivityOptions activityOptions = ActivityOptions.makeBasic()
+                            .setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    mContext.startIntentSender(intent, null, 0, 0, 0,
+                            activityOptions.toBundle());
                     return new PrintJob(printJob, this);
                 } catch (SendIntentException sie) {
                     Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7e4e402..5019b85 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1099,21 +1099,25 @@
         // TODO: Support a ResultReceiver for IME.
         // TODO(b/123718661): Make show() work for multi-session IME.
         int typesReady = 0;
+        final boolean imeVisible = mState.isSourceOrDefaultVisible(
+                mImeSourceConsumer.getId(), ime());
         for (int type = FIRST; type <= LAST; type = type << 1) {
             if ((types & type) == 0) {
                 continue;
             }
             @AnimationType final int animationType = getAnimationType(type);
             final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
-            final boolean isImeAnimation = type == ime();
-            if (requestedVisible && animationType == ANIMATION_TYPE_NONE
-                    || animationType == ANIMATION_TYPE_SHOW) {
+            final boolean isIme = type == ime();
+            var alreadyVisible = requestedVisible && (!isIme || imeVisible)
+                    && animationType == ANIMATION_TYPE_NONE;
+            var alreadyAnimatingShow = animationType == ANIMATION_TYPE_SHOW;
+            if (alreadyVisible || alreadyAnimatingShow) {
                 // no-op: already shown or animating in (because window visibility is
                 // applied before starting animation).
                 if (DEBUG) Log.d(TAG, String.format(
                         "show ignored for type: %d animType: %d requestedVisible: %s",
                         type, animationType, requestedVisible));
-                if (isImeAnimation) {
+                if (isIme) {
                     ImeTracker.forLogging().onCancelled(statsToken,
                             ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
@@ -1121,13 +1125,13 @@
             }
             if (fromIme && animationType == ANIMATION_TYPE_USER) {
                 // App is already controlling the IME, don't cancel it.
-                if (isImeAnimation) {
+                if (isIme) {
                     ImeTracker.forLogging().onFailed(
                             statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
             }
-            if (isImeAnimation) {
+            if (isIme) {
                 ImeTracker.forLogging().onProgress(
                         statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
             }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 3d95dd3..c9e7600 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,6 +81,10 @@
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
+
+        /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
+        public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
+                devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
     }
 
     //// == End of flags.  Everything below this line is the implementation. == ////
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fb3acbe..a5b2b85 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5035,8 +5035,8 @@
     <string name="display_rotation_camera_compat_toast_after_rotation">Rotate for a better view</string>
 
     <!-- Text on a toast shown when a camera view is started within the app that may not be able
-         to display the camera preview correctly while in split screen. [CHAR LIMIT=NONE] -->
-    <string name="display_rotation_camera_compat_toast_in_split_screen">Exit split screen for a better view</string>
+         to display the camera preview correctly while in multi-window. [CHAR LIMIT=NONE] -->
+    <string name="display_rotation_camera_compat_toast_in_multi_window">Open <xliff:g id="name" example="MyApp">%s</xliff:g> in full screen for a better view</string>
 
     <!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] -->
     <string name="done_label">Done</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6ab671a..a8518af 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2533,7 +2533,7 @@
   <java-symbol type="string" name="zen_mode_default_events_name" />
   <java-symbol type="string" name="zen_mode_default_every_night_name" />
   <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
-  <java-symbol type="string" name="display_rotation_camera_compat_toast_in_split_screen" />
+  <java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" />
   <java-symbol type="array" name="config_system_condition_providers" />
   <java-symbol type="string" name="muted_by" />
   <java-symbol type="string" name="zen_mode_alarm" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index e4defcf..93e44f1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1813,6 +1813,12 @@
       "group": "WM_DEBUG_KEEP_SCREEN_ON",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-479665533": {
+      "message": "DisplayRotationCompatPolicy: Multi-window toast not shown as package '%s' cannot be found.",
+      "level": "ERROR",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-464564167": {
       "message": "Current transition prevents automatic focus change",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index 82fe38c..7971c04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -135,14 +135,14 @@
                 maxWidth = (int) (mOptimizedAspectRatio * shorterLength
                         + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
                         + aspectRatio));
-                maxHeight = (int) (maxWidth / aspectRatio);
+                maxHeight = Math.round(maxWidth / aspectRatio);
             } else {
                 if (aspectRatio > 1f) {
                     maxWidth = shorterLength;
-                    maxHeight = (int) (maxWidth / aspectRatio);
+                    maxHeight = Math.round(maxWidth / aspectRatio);
                 } else {
                     maxHeight = shorterLength;
-                    maxWidth = (int) (maxHeight * aspectRatio);
+                    maxWidth = Math.round(maxHeight * aspectRatio);
                 }
             }
 
@@ -165,10 +165,9 @@
 
             Size maxSize = this.getMaxSize(aspectRatio);
 
-            int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
+            int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
                     minSize.getWidth());
-            int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
-                    minSize.getHeight());
+            int defaultHeight = Math.round(defaultWidth / aspectRatio);
 
             return new Size(defaultWidth, defaultHeight);
         }
@@ -188,16 +187,16 @@
 
             Size maxSize = this.getMaxSize(aspectRatio);
 
-            int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
-            int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
+            int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
+            int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
 
             // make sure the calculated min size is not smaller than the allowed default min size
             if (aspectRatio > 1f) {
-                minHeight = (int) Math.max(minHeight, mDefaultMinSize);
-                minWidth = (int) (minHeight * aspectRatio);
+                minHeight = Math.max(minHeight, mDefaultMinSize);
+                minWidth = Math.round(minHeight * aspectRatio);
             } else {
-                minWidth = (int) Math.max(minWidth, mDefaultMinSize);
-                minHeight = (int) (minWidth / aspectRatio);
+                minWidth = Math.max(minWidth, mDefaultMinSize);
+                minHeight = Math.round(minWidth / aspectRatio);
             }
             return new Size(minWidth, minHeight);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e616965..38911db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1482,6 +1482,10 @@
         }
         // If running background, we need to reparent current top visible task to main stage.
         if (!isSplitScreenVisible()) {
+            // Ensure to evict old splitting tasks because the new split pair might be composed by
+            // one of the splitting tasks, evicting the task when finishing entering transition
+            // won't guarantee to put the task to the indicated new position.
+            mMainStage.evictAllChildren(wct);
             mMainStage.reparentTopTask(wct);
             prepareSplitLayout(wct);
         }
@@ -2311,8 +2315,9 @@
                     mSplitTransitions.setDismissTransition(transition, dismissTop,
                             EXIT_REASON_APP_FINISHED);
                 } else if (!isSplitScreenVisible() && isOpening) {
-                    // If split running backgroud and trigger task is appearing into split,
-                    // prepare to enter split screen.
+                    // If split is running in the background and the trigger task is appearing into
+                    // split, prepare to enter split screen.
+                    setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, out);
                     prepareEnterSplitScreen(out);
                     mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
                             TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
@@ -2338,6 +2343,7 @@
             if (isOpening && getStageOfTask(triggerTask) != null) {
                 // One task is appearing into split, prepare to enter split screen.
                 out = new WindowContainerTransaction();
+                setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, out);
                 prepareEnterSplitScreen(out);
                 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
                         TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
@@ -2901,7 +2907,8 @@
         }
 
         setSplitsVisible(false);
-        prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+        finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                true /* reparentLeafTaskIfRelaunch */);
         logExit(EXIT_REASON_UNKNOWN);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 425bbf0..1379aed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -106,21 +106,21 @@
         sExpectedDefaultSizes = new HashMap<>();
         sExpectedMinSizes = new HashMap<>();
 
-        sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
-        sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
-        sExpectedMinSizes.put(16f / 9, new Size(499, 281));
+        sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+        sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
+        sExpectedMinSizes.put(16f / 9, new Size(501, 282));
 
         sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
         sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
-        sExpectedMinSizes.put(4f / 3, new Size(445, 334));
+        sExpectedMinSizes.put(4f / 3, new Size(447, 335));
 
         sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
         sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
-        sExpectedMinSizes.put(3f / 4, new Size(334, 445));
+        sExpectedMinSizes.put(3f / 4, new Size(335, 447));
 
         sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
         sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
-        sExpectedMinSizes.put(9f / 16, new Size(281, 499));
+        sExpectedMinSizes.put(9f / 16, new Size(281, 500));
     }
 
     private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 3527eee..2a6dc7b 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -108,7 +108,7 @@
 
     {
         // Migrate existing contents into new ashmem region
-        uint32_t slotsSize = mSize - mSlotsOffset;
+        uint32_t slotsSize = sizeOfSlots();
         uint32_t newSlotsOffset = mInflatedSize - slotsSize;
         memcpy(static_cast<uint8_t*>(newData),
                 static_cast<uint8_t*>(mData), mAllocOffset);
@@ -216,11 +216,9 @@
         if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
     } else {
         // Since we know we're going to be read-only on the remote side,
-        // we can compact ourselves on the wire, with just enough padding
-        // to ensure our slots stay aligned
-        size_t slotsSize = mSize - mSlotsOffset;
-        size_t compactedSize = mAllocOffset + slotsSize;
-        compactedSize = (compactedSize + 3) & ~3;
+        // we can compact ourselves on the wire.
+        size_t slotsSize = sizeOfSlots();
+        size_t compactedSize = sizeInUse();
         if (parcel->writeUint32(compactedSize)) goto fail;
         if (parcel->writeBool(false)) goto fail;
         void* dest = parcel->writeInplace(compactedSize);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index 6e55a9a..9ec026a 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -90,6 +90,9 @@
     inline uint32_t getNumRows() { return mNumRows; }
     inline uint32_t getNumColumns() { return mNumColumns; }
 
+    inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; }
+    inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); }
+
     status_t clear();
     status_t setNumColumns(uint32_t numColumns);
 
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index d1cfd03..3cdb31e 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -21,9 +21,16 @@
 
 #include "TestHelpers.h"
 
+// Verify that the memory in use is a multiple of 4 bytes
+#define ASSERT_ALIGNED(w) \
+    ASSERT_EQ(((w)->sizeInUse() & 3), 0); \
+    ASSERT_EQ(((w)->freeSpace() & 3), 0); \
+    ASSERT_EQ(((w)->sizeOfSlots() & 3), 0)
+
 #define CREATE_WINDOW_1K \
     CursorWindow* w; \
-    CursorWindow::create(String8("test"), 1 << 10, &w);
+    CursorWindow::create(String8("test"), 1 << 10, &w); \
+    ASSERT_ALIGNED(w);
 
 #define CREATE_WINDOW_1K_3X3 \
     CursorWindow* w; \
@@ -31,11 +38,13 @@
     ASSERT_EQ(w->setNumColumns(3), OK); \
     ASSERT_EQ(w->allocRow(), OK); \
     ASSERT_EQ(w->allocRow(), OK); \
-    ASSERT_EQ(w->allocRow(), OK);
+    ASSERT_EQ(w->allocRow(), OK); \
+    ASSERT_ALIGNED(w);
 
 #define CREATE_WINDOW_2M \
     CursorWindow* w; \
-    CursorWindow::create(String8("test"), 1 << 21, &w);
+    CursorWindow::create(String8("test"), 1 << 21, &w); \
+    ASSERT_ALIGNED(w);
 
 static constexpr const size_t kHalfInlineSize = 8192;
 static constexpr const size_t kGiantSize = 1048576;
@@ -49,6 +58,7 @@
     ASSERT_EQ(w->getNumColumns(), 0);
     ASSERT_EQ(w->size(), 1 << 10);
     ASSERT_EQ(w->freeSpace(), 1 << 10);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, SetNumColumns) {
@@ -60,6 +70,7 @@
     ASSERT_NE(w->setNumColumns(5), OK);
     ASSERT_NE(w->setNumColumns(3), OK);
     ASSERT_EQ(w->getNumColumns(), 4);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, SetNumColumnsAfterRow) {
@@ -70,6 +81,7 @@
     ASSERT_EQ(w->allocRow(), OK);
     ASSERT_NE(w->setNumColumns(4), OK);
     ASSERT_EQ(w->getNumColumns(), 0);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, AllocRow) {
@@ -83,14 +95,17 @@
     ASSERT_EQ(w->allocRow(), OK);
     ASSERT_LT(w->freeSpace(), before);
     ASSERT_EQ(w->getNumRows(), 1);
+    ASSERT_ALIGNED(w);
 
     // Verify we can unwind
     ASSERT_EQ(w->freeLastRow(), OK);
     ASSERT_EQ(w->freeSpace(), before);
     ASSERT_EQ(w->getNumRows(), 0);
+    ASSERT_ALIGNED(w);
 
     // Can't unwind when no rows left
     ASSERT_NE(w->freeLastRow(), OK);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, AllocRowBounds) {
@@ -100,6 +115,7 @@
     ASSERT_EQ(w->setNumColumns(60), OK);
     ASSERT_EQ(w->allocRow(), OK);
     ASSERT_NE(w->allocRow(), OK);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreNull) {
@@ -116,6 +132,7 @@
         auto field = w->getFieldSlot(0, 0);
         ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreLong) {
@@ -134,6 +151,7 @@
         ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
         ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreString) {
@@ -155,6 +173,7 @@
         auto actual = w->getFieldSlotValueString(field, &size);
         ASSERT_EQ(std::string(actual), "cafe");
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreBounds) {
@@ -175,6 +194,7 @@
     ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr);
     ASSERT_EQ(w->getFieldSlot(0, -1), nullptr);
     ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, Inflate) {
@@ -234,6 +254,7 @@
         ASSERT_NE(actual, buf);
         ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, ParcelEmpty) {
@@ -249,10 +270,12 @@
     ASSERT_EQ(w->getNumColumns(), 0);
     ASSERT_EQ(w->size(), 0);
     ASSERT_EQ(w->freeSpace(), 0);
+    ASSERT_ALIGNED(w);
 
     // We can't mutate the window after parceling
     ASSERT_NE(w->setNumColumns(4), OK);
     ASSERT_NE(w->allocRow(), OK);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, ParcelSmall) {
@@ -311,6 +334,7 @@
         ASSERT_EQ(actualSize, 0);
         ASSERT_NE(actual, nullptr);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, ParcelLarge) {
@@ -364,6 +388,7 @@
         ASSERT_EQ(actualSize, 0);
         ASSERT_NE(actual, nullptr);
     }
+    ASSERT_ALIGNED(w);
 }
 
 } // android
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dac7f8d..b44c0e0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -929,7 +929,7 @@
     <!-- UI debug setting: show all ANRs summary [CHAR LIMIT=100] -->
     <string name="show_all_anrs_summary">Display App Not Responding dialog for background apps</string>
 
-    <!-- UI debug setting: show missing channel toasts? [CHAR LIMIT=25] -->
+    <!-- UI debug setting: show missing channel toasts? [CHAR LIMIT=30] -->
     <string name="show_notification_channel_warnings">Show notification channel warnings</string>
     <!-- UI debug setting: show missing channel toasts summary [CHAR LIMIT=50] -->
     <string name="show_notification_channel_warnings_summary">Displays on-screen warning when an app posts a notification without a valid channel</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fe90caf..0a9a184 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -913,8 +913,9 @@
                   android:excludeFromRecents="true"
                   android:launchMode="singleInstance"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
-                  android:visibleToInstantApps="true">
-        </activity>
+                  android:visibleToInstantApps="true"
+                  android:exported="true"
+                  />
 
         <activity android:name=".controls.management.ControlsEditingActivity"
                   android:label="@string/controls_menu_edit"
@@ -947,8 +948,9 @@
                   android:finishOnTaskLaunch="true"
                   android:launchMode="singleInstance"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|orientation"
-                  android:visibleToInstantApps="true">
-        </activity>
+                  android:visibleToInstantApps="true"
+                  android:exported="true"
+                  />
 
         <activity android:name=".wallet.ui.WalletActivity"
                   android:label="@string/wallet_title"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 89871fa7..2cd587f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -56,7 +56,19 @@
     val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
     val pixelDensity: Float = 1f,
     val blendMode: BlendMode = DEFAULT_BLEND_MODE,
-    val onAnimationEnd: Runnable? = null
+    val onAnimationEnd: Runnable? = null,
+    /**
+     * Variants in noise. Higher number means more contrast; lower number means less contrast but
+     * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate.
+     * Expected range [0, 1].
+     */
+    val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR,
+    /**
+     * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may
+     * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected
+     * range [0, 1].
+     */
+    val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS
 ) {
     companion object {
         const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -66,6 +78,8 @@
         const val DEFAULT_NOISE_SPEED_Z = 0.3f
         const val DEFAULT_OPACITY = 150 // full opacity is 255.
         const val DEFAULT_COLOR = Color.WHITE
+        const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
+        const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
         const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
         val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index d1ba7c4..d3c57c9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -37,6 +37,8 @@
             uniform float in_opacity;
             uniform float in_pixelDensity;
             uniform float in_inverseLuma;
+            uniform half in_lumaMatteBlendFactor;
+            uniform half in_lumaMatteOverallBrightness;
             layout(color) uniform vec4 in_color;
             layout(color) uniform vec4 in_backgroundColor;
         """
@@ -48,18 +50,21 @@
                 uv.x *= in_aspectRatio;
 
                 vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
-                float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity;
+                // Bring it to [0, 1] range.
+                float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+                luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+                        * in_opacity;
                 vec3 mask = maskLuminosity(in_color.rgb, luma);
                 vec3 color = in_backgroundColor.rgb + mask * 0.6;
 
-                // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+                // Add dither with triangle distribution to avoid color banding. Dither in the
                 // shader here as we are in gamma space.
                 float dither = triangleNoise(p * in_pixelDensity) / 255.;
 
                 // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
                 // multiply rgb with a to get the correct result.
-                color = (color + dither.rrr) * in_color.a;
-                return vec4(color, in_color.a);
+                color = (color + dither.rrr) * in_opacity;
+                return vec4(color, in_opacity);
             }
         """
 
@@ -70,12 +75,15 @@
                 uv.x *= in_aspectRatio;
 
                 vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
-                float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity;
+                // Bring it to [0, 1] range.
+                float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+                luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+                        * in_opacity;
                 vec3 mask = maskLuminosity(in_color.rgb, luma);
                 vec3 color = in_backgroundColor.rgb + mask * 0.6;
 
                 // Skip dithering.
-                return vec4(color * in_color.a, in_color.a);
+                return vec4(color * in_opacity, in_opacity);
             }
         """
 
@@ -125,6 +133,28 @@
     }
 
     /**
+     * Sets blend and brightness factors of the luma matte.
+     *
+     * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
+     *   this a lower number removes variations. I.e. the turbulence noise will look more blended.
+     *   Expected input range is [0, 1]. more dimmed.
+     * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
+     *   Expected input range is [0, 1].
+     *
+     * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2,
+     * which makes the noise look softer. However it makes the overall noise look dim, so you want
+     * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall
+     * brightness.
+     */
+    fun setLumaMatteFactors(
+        lumaMatteBlendFactor: Float = 1f,
+        lumaMatteOverallBrightness: Float = 0f
+    ) {
+        setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor)
+        setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness)
+    }
+
+    /**
      * Sets whether to inverse the luminosity of the noise.
      *
      * By default noise will be used as a luma matte as is. This means that you will see color in
@@ -132,7 +162,7 @@
      * true.
      */
     fun setInverseNoiseLuminosity(inverse: Boolean) {
-        setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f)
+        setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f)
     }
 
     /** Current noise movements in x, y, and z axes. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c3e8478..43d6504 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -215,10 +215,12 @@
         noiseConfig = config
         with(turbulenceNoiseShader) {
             setGridCount(config.gridCount)
-            setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+            setColor(config.color)
             setBackgroundColor(config.backgroundColor)
             setSize(config.width, config.height)
             setPixelDensity(config.pixelDensity)
+            setInverseNoiseLuminosity(inverse = false)
+            setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
         }
         paint.blendMode = config.blendMode
     }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index cd9fb88..519ae0a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -163,15 +163,14 @@
          */
         val enablementActionText: String? = null,
         /**
-         * If the affordance is disabled, this is a "component name" of the format
-         * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
-         * (shown together with the set of instruction messages when the disabled affordance is
-         * selected) is clicked by the user. The button should help send the user to a flow that
-         * would help them achieve the instructions and re-enable this affordance.
+         * If the affordance is disabled, this is an [Intent] to be used with `startActivity` when
+         * the action button (shown together with the set of instruction messages when the disabled
+         * affordance is selected) is clicked by the user. The button should help send the user to a
+         * flow that would help them achieve the instructions and re-enable this affordance.
          *
          * If `null`, the button should not be shown.
          */
-        val enablementActionComponentName: String? = null,
+        val enablementActionIntent: Intent? = null,
         /** Optional [Intent] to use to start an activity to configure this affordance. */
         val configureIntent: Intent? = null,
     )
@@ -337,10 +336,10 @@
                                 Contract.LockScreenQuickAffordances.AffordanceTable.Columns
                                     .ENABLEMENT_ACTION_TEXT
                             )
-                        val enablementComponentNameColumnIndex =
+                        val enablementActionIntentColumnIndex =
                             cursor.getColumnIndex(
                                 Contract.LockScreenQuickAffordances.AffordanceTable.Columns
-                                    .ENABLEMENT_COMPONENT_NAME
+                                    .ENABLEMENT_ACTION_INTENT
                             )
                         val configureIntentColumnIndex =
                             cursor.getColumnIndex(
@@ -354,7 +353,7 @@
                                 isEnabledColumnIndex == -1 ||
                                 enablementInstructionsColumnIndex == -1 ||
                                 enablementActionTextColumnIndex == -1 ||
-                                enablementComponentNameColumnIndex == -1 ||
+                                enablementActionIntentColumnIndex == -1 ||
                                 configureIntentColumnIndex == -1
                         ) {
                             return@buildList
@@ -377,12 +376,18 @@
                                             ),
                                     enablementActionText =
                                         cursor.getString(enablementActionTextColumnIndex),
-                                    enablementActionComponentName =
-                                        cursor.getString(enablementComponentNameColumnIndex),
+                                    enablementActionIntent =
+                                        cursor
+                                            .getString(enablementActionIntentColumnIndex)
+                                            ?.toIntent(
+                                                affordanceId = affordanceId,
+                                            ),
                                     configureIntent =
                                         cursor
                                             .getString(configureIntentColumnIndex)
-                                            ?.toIntent(affordanceId = affordanceId),
+                                            ?.toIntent(
+                                                affordanceId = affordanceId,
+                                            ),
                                 )
                             )
                         }
@@ -524,7 +529,7 @@
         affordanceId: String,
     ): Intent? {
         return try {
-            Intent.parseUri(this, 0)
+            Intent.parseUri(this, Intent.URI_INTENT_SCHEME)
         } catch (e: URISyntaxException) {
             Log.w(TAG, "Cannot parse Uri into Intent for affordance with ID \"$affordanceId\"!")
             null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index f9e8aaf..7f5fb25 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -82,7 +82,6 @@
             val URI: Uri =
                 LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build()
             const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
-            const val COMPONENT_NAME_SEPARATOR = "/"
 
             object Columns {
                 /** String. Unique ID for this affordance. */
@@ -108,11 +107,11 @@
                  */
                 const val ENABLEMENT_ACTION_TEXT = "enablement_action_text"
                 /**
-                 * String. Optional package name and activity action string, delimited by
-                 * [COMPONENT_NAME_SEPARATOR] to use with an `Intent` to start an activity that
-                 * opens a destination where the user can re-enable the disabled affordance.
+                 * String. Optional URI-formatted `Intent` (formatted using
+                 * `Intent#toUri(Intent.URI_INTENT_SCHEME)` used to start an activity that opens a
+                 * destination where the user can re-enable the disabled affordance.
                  */
-                const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent"
+                const val ENABLEMENT_ACTION_INTENT = "enablement_action_intent"
                 /**
                  * Byte array. Optional parcelled `Intent` to use to start an activity that can be
                  * used to configure the affordance.
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index a8f0cc3..4a9d41f 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,20 +14,6 @@
     limitations under the License.
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-        android:insetLeft="3dp"
-        android:insetRight="3dp">
-    <vector android:width="18dp"
-            android:height="18dp"
-            android:viewportWidth="24.0"
-            android:viewportHeight="24.0">
-        <path
-            android:fillColor="#FFFFFFFF"
-            android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/>
-        <path
-            android:fillColor="#FFFFFFFF"
-            android:pathData="M16,16L2.81,2.81L1.39,4.22l4.85,4.85C6.09,9.68 6,10.33 6,11v6H4v2h12.17l3.61,3.61l1.41,-1.41L16,16zM8,17c0,0 0.01,-6.11 0.01,-6.16L14.17,17H8z"/>
-        <path
-            android:fillColor="#FFFFFFFF"
-            android:pathData="M12,6.5c2.49,0 4,2.02 4,4.5v2.17l2,2V11c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.72,4.86 9.05,5.2 8.46,5.63L9.93,7.1C10.51,6.73 11.2,6.5 12,6.5z"/>
-    </vector>
-</inset>
+    android:insetLeft="3dp"
+    android:insetRight="3dp"
+    android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c3651cf..166bd2a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -902,4 +902,16 @@
     <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
     device entry. -->
     <integer name="primary_bouncer_passive_auth_delay">500</integer>
+
+    <!--
+    The package name of the app store app. If empty, features using this should be gracefully
+    disabled.
+    -->
+    <string name="config_appStorePackageName" translatable="false"></string>
+
+    <!-- Template for a link that leads to an app page in the relevant app store. If empty,
+    features using this should be gracefully disabled. If not empty, it must include a
+    "$packageName" part that will be replaced by the code with the package name of the target app.
+    -->
+    <string name="config_appStoreAppLinkTemplate" translatable="false"></string>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8d3ba36..4f768cc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -572,6 +572,7 @@
     <dimen name="qs_brightness_margin_bottom">16dp</dimen>
     <dimen name="qqs_layout_margin_top">16dp</dimen>
     <dimen name="qqs_layout_padding_bottom">24dp</dimen>
+    <item name="qqs_expand_clock_scale" format="float" type="dimen">2.57</item>
 
     <!-- Most of the time it should be the same as notification_side_paddings as it's vertically
          aligned with notifications. The exception is split shade when this value becomes
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b48296fe..e779672 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2724,8 +2724,8 @@
     <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
     <!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
     <string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
-    <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
-    <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
+    <!-- The hint message when Broadcast edit is more than 16/254 characters [CHAR LIMIT=60] -->
+    <string name="media_output_broadcast_edit_hint_no_more_than_max">Use fewer than <xliff:g id="length" example="16">%1$d</xliff:g> characters</string>
 
     <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
     <string name="build_number_clip_data_label">Build number</string>
@@ -3153,4 +3153,11 @@
 
     <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
     <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
+
+    <!--
+    Label for a button that, when clicked, sends the user to the app store to install an app.
+
+    [CHAR LIMIT=64].
+    -->
+    <string name="install_app">Install app</string>
 </resources>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 52a98984..8039c68 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -43,8 +43,8 @@
             app:layout_constraintBottom_toBottomOf="@id/carrier_group"
         />
         <Transform
-            android:scaleX="2.57"
-            android:scaleY="2.57"
+            android:scaleX="@dimen/qqs_expand_clock_scale"
+            android:scaleY="@dimen/qqs_expand_clock_scale"
             />
     </Constraint>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1721891..83c317f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3565,7 +3565,6 @@
      */
     private void handleTimeUpdate() {
         Assert.isMainThread();
-        mLogger.d("handleTimeUpdate");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -3630,9 +3629,9 @@
     private void handleBatteryUpdate(BatteryStatus status) {
         Assert.isMainThread();
         final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
-        mLogger.logHandleBatteryUpdate(batteryUpdateInteresting);
         mBatteryStatus = status;
         if (batteryUpdateInteresting) {
+            mLogger.logHandleBatteryUpdate(mBatteryStatus);
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 4923ab0..b596331 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -30,7 +30,7 @@
 import com.android.keyguard.KeyguardListenModel
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.TrustGrantFlags
-import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.settingslib.fuelgauge.BatteryStatus
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogLevel.DEBUG
@@ -38,6 +38,7 @@
 import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.LogLevel.VERBOSE
 import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -683,8 +684,27 @@
         )
     }
 
-    fun logHandleBatteryUpdate(isInteresting: Boolean) {
-        logBuffer.log(TAG, DEBUG, { bool1 = isInteresting }, { "handleBatteryUpdate: $bool1" })
+    fun logHandleBatteryUpdate(batteryStatus: BatteryStatus?) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = batteryStatus != null
+                int1 = batteryStatus?.status ?: -1
+                int2 = batteryStatus?.chargingStatus ?: -1
+                long1 = (batteryStatus?.level ?: -1).toLong()
+                long2 = (batteryStatus?.maxChargingWattage ?: -1).toLong()
+                str1 = "${batteryStatus?.plugged ?: -1}"
+            },
+            {
+                "handleBatteryUpdate: isNotNull: $bool1 " +
+                    "BatteryStatus{status= $int1, " +
+                    "level=$long1, " +
+                    "plugged=$str1, " +
+                    "chargingStatus=$int2, " +
+                    "maxChargingWattage= $long2}"
+            }
+        )
     }
 
     fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f86e2ed..7f70685 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -614,7 +614,11 @@
             return ((AuthBiometricFingerprintView) view).isUdfps();
         }
         if (view instanceof BiometricPromptLayout) {
-            return ((BiometricPromptLayout) view).isUdfps();
+            // this will force the prompt to align itself on the edge of the screen
+            // instead of centering (temporary workaround to prevent small implicit view
+            // from breaking due to the way gravity / margins are set in the legacy
+            // AuthPanelController
+            return true;
         }
 
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index baaa96e..d48b9c33 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -35,6 +35,7 @@
 import android.util.Log
 import android.util.RotationUtils
 import android.view.Display
+import android.view.DisplayInfo
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.Surface
@@ -58,6 +59,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.util.boundsOnScreen
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.traceSection
 import java.io.PrintWriter
@@ -129,6 +131,8 @@
         }
     @VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
 
+    private val displayInfo = DisplayInfo()
+
     private val overlayViewParams =
         WindowManager.LayoutParams(
                 WindowManager.LayoutParams.WRAP_CONTENT,
@@ -214,6 +218,23 @@
         for (requestSource in requests) {
             pw.println("     $requestSource.name")
         }
+
+        pw.println("overlayView:")
+        pw.println("     width=${overlayView?.width}")
+        pw.println("     height=${overlayView?.height}")
+        pw.println("     boundsOnScreen=${overlayView?.boundsOnScreen}")
+
+        pw.println("displayStateInteractor:")
+        pw.println("     isInRearDisplayMode=${displayStateInteractor?.isInRearDisplayMode?.value}")
+
+        pw.println("sensorProps:")
+        pw.println("     displayId=${displayInfo.uniqueId}")
+        pw.println("     sensorType=${sensorProps?.sensorType}")
+        pw.println("     location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+
+        pw.println("overlayOffsets=$overlayOffsets")
+        pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
+        pw.println("currentRotation=${displayInfo.rotation}")
     }
 
     private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
@@ -226,6 +247,8 @@
         val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
         overlayView = view
         val display = context.display!!
+        // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
+        display.getDisplayInfo(displayInfo)
         val offsets =
             sensorProps.getLocation(display.uniqueId).let { location ->
                 if (location == null) {
@@ -239,12 +262,12 @@
         view.rotation =
             display.asSideFpsAnimationRotation(
                 offsets.isYAligned(),
-                getRotationFromDefault(display.rotation)
+                getRotationFromDefault(displayInfo.rotation)
             )
         lottie.setAnimation(
             display.asSideFpsAnimation(
                 offsets.isYAligned(),
-                getRotationFromDefault(display.rotation)
+                getRotationFromDefault(displayInfo.rotation)
             )
         )
         lottie.addLottieOnCompositionLoadedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e4c4e9a..1dffa80 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,8 +19,11 @@
 import android.animation.Animator
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
+import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.widget.TextView
 import androidx.core.animation.addListener
@@ -52,7 +55,9 @@
         panelViewController: AuthPanelController,
         jankListener: BiometricJankListener,
     ) {
-        val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+        val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
+        val accessibilityManager =
+            requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
         fun notifyAccessibilityChanged() {
             Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
         }
@@ -102,15 +107,26 @@
                             when {
                                 size.isSmall -> {
                                     iconHolderView.alpha = 1f
+                                    val bottomInset =
+                                        windowManager.maximumWindowMetrics.windowInsets
+                                            .getInsets(WindowInsets.Type.navigationBars())
+                                            .bottom
                                     iconHolderView.y =
-                                        view.height - iconHolderView.height - iconPadding
+                                        if (view.isLandscape()) {
+                                            (view.height - iconHolderView.height - bottomInset) / 2f
+                                        } else {
+                                            view.height -
+                                                iconHolderView.height -
+                                                iconPadding -
+                                                bottomInset
+                                        }
                                     val newHeight =
-                                        iconHolderView.height + 2 * iconPadding.toInt() -
+                                        iconHolderView.height + (2 * iconPadding.toInt()) -
                                             iconHolderView.paddingTop -
                                             iconHolderView.paddingBottom
                                     panelViewController.updateForContentDimensions(
                                         width,
-                                        newHeight,
+                                        newHeight + bottomInset,
                                         0, /* animateDurationMs */
                                     )
                                 }
@@ -181,6 +197,11 @@
     }
 }
 
+private fun View.isLandscape(): Boolean {
+    val r = context.display.rotation
+    return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+}
+
 private fun TextView.showTextOrHide(forceHide: Boolean = false) {
     visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
index d7d1700..c921806 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.controls.controller
 
 interface ControlsTileResourceConfiguration {
+    fun getPackageName(): String?
     fun getTileTitleId(): Int
     fun getTileImageId(): Int
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
index c96d3d4..0249060 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
@@ -20,12 +20,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 
-/**
- * Default Instance for ControlsTileResourceConfiguration.
- */
+/** Default Instance for ControlsTileResourceConfiguration. */
 @SysUISingleton
-class ControlsTileResourceConfigurationImpl @Inject constructor()
-    : ControlsTileResourceConfiguration {
+class ControlsTileResourceConfigurationImpl @Inject constructor() :
+    ControlsTileResourceConfiguration {
+    override fun getPackageName(): String? {
+        return null
+    }
+
     override fun getTileTitleId(): Int {
         return R.string.quick_controls_title
     }
@@ -33,4 +35,4 @@
     override fun getTileImageId(): Int {
         return R.drawable.controls_icon
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 7509a8a..94e5633 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -29,9 +29,9 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
-import kotlinx.coroutines.flow.StateFlow
 import java.util.Optional
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -40,26 +40,26 @@
  * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls.
  */
 @SysUISingleton
-class ControlsComponent @Inject constructor(
-        @ControlsFeatureEnabled private val featureEnabled: Boolean,
-        private val context: Context,
-        private val lazyControlsController: Lazy<ControlsController>,
-        private val lazyControlsUiController: Lazy<ControlsUiController>,
-        private val lazyControlsListingController: Lazy<ControlsListingController>,
-        private val lockPatternUtils: LockPatternUtils,
-        private val keyguardStateController: KeyguardStateController,
-        private val userTracker: UserTracker,
-        controlsSettingsRepository: ControlsSettingsRepository,
-        optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
+class ControlsComponent
+@Inject
+constructor(
+    @ControlsFeatureEnabled private val featureEnabled: Boolean,
+    private val context: Context,
+    private val lazyControlsController: Lazy<ControlsController>,
+    private val lazyControlsUiController: Lazy<ControlsUiController>,
+    private val lazyControlsListingController: Lazy<ControlsListingController>,
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardStateController: KeyguardStateController,
+    private val userTracker: UserTracker,
+    controlsSettingsRepository: ControlsSettingsRepository,
+    optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
 ) {
 
     val canShowWhileLockedSetting: StateFlow<Boolean> =
-            controlsSettingsRepository.canShowControlsInLockscreen
+        controlsSettingsRepository.canShowControlsInLockscreen
 
     private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
-        optionalControlsTileResourceConfiguration.orElse(
-            ControlsTileResourceConfigurationImpl()
-        )
+        optionalControlsTileResourceConfiguration.orElse(ControlsTileResourceConfigurationImpl())
 
     fun getControlsController(): Optional<ControlsController> {
         return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
@@ -77,9 +77,7 @@
         }
     }
 
-    /**
-     * @return true if controls are feature-enabled and the user has the setting enabled
-     */
+    /** @return true if controls are feature-enabled and the user has the setting enabled */
     fun isEnabled() = featureEnabled
 
     /**
@@ -90,8 +88,10 @@
      */
     fun getVisibility(): Visibility {
         if (!isEnabled()) return Visibility.UNAVAILABLE
-        if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
-                == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
+        if (
+            lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier) ==
+                STRONG_AUTH_REQUIRED_AFTER_BOOT
+        ) {
             return Visibility.AVAILABLE_AFTER_UNLOCK
         }
         if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
@@ -102,7 +102,13 @@
     }
 
     enum class Visibility {
-        AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
+        AVAILABLE,
+        AVAILABLE_AFTER_UNLOCK,
+        UNAVAILABLE
+    }
+
+    fun getPackageName(): String? {
+        return controlsTileResourceConfiguration.getPackageName()
     }
 
     fun getTileTitleId(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5fa209..a7e5c22 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -728,6 +728,13 @@
     @JvmField
     val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
 
+    // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name>
+
+    // TODO:(b/285623104): Tracking bug
+    @JvmField
+    val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
+        releasedFlag(2900, "zj_285570694_lockscreen_transition_from_aod")
+
     // TODO(b/283084712): Tracking Bug
     @JvmField
     val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 27a5974..064a44a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -20,6 +20,7 @@
 import android.content.ContentProvider
 import android.content.ContentValues
 import android.content.Context
+import android.content.Intent
 import android.content.UriMatcher
 import android.content.pm.PackageManager
 import android.content.pm.ProviderInfo
@@ -286,7 +287,7 @@
                     Contract.LockScreenQuickAffordances.AffordanceTable.Columns
                         .ENABLEMENT_ACTION_TEXT,
                     Contract.LockScreenQuickAffordances.AffordanceTable.Columns
-                        .ENABLEMENT_COMPONENT_NAME,
+                        .ENABLEMENT_ACTION_INTENT,
                     Contract.LockScreenQuickAffordances.AffordanceTable.Columns.CONFIGURE_INTENT,
                 )
             )
@@ -303,8 +304,8 @@
                                     .ENABLEMENT_INSTRUCTIONS_DELIMITER
                             ),
                             representation.actionText,
-                            representation.actionComponentName,
-                            representation.configureIntent?.toUri(0),
+                            representation.actionIntent?.toUri(Intent.URI_INTENT_SCHEME),
+                            representation.configureIntent?.toUri(Intent.URI_INTENT_SCHEME),
                         )
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index abb63c4..0991b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import androidx.annotation.DrawableRes
@@ -34,6 +35,7 @@
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.appStoreIntent
 import com.android.systemui.util.kotlin.getOrNull
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -76,18 +78,31 @@
             component.getControlsListingController().getOrNull()?.getCurrentServices()
         val hasFavorites =
             component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
-        if (currentServices.isNullOrEmpty() || !hasFavorites) {
-            return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
-                instructions =
-                    listOf(
-                        context.getString(
-                            R.string.keyguard_affordance_enablement_dialog_home_instruction_1
-                        ),
-                        context.getString(
-                            R.string.keyguard_affordance_enablement_dialog_home_instruction_2
-                        ),
-                    ),
-            )
+        val componentPackageName = component.getPackageName()
+        when {
+            currentServices.isNullOrEmpty() && !componentPackageName.isNullOrEmpty() -> {
+                // No home app installed but we know which app we want to install.
+                return disabledPickerState(
+                    actionText = context.getString(R.string.install_app),
+                    actionIntent = appStoreIntent(context, componentPackageName),
+                )
+            }
+            currentServices.isNullOrEmpty() && componentPackageName.isNullOrEmpty() -> {
+                // No home app installed and we don't know which app we want to install.
+                return disabledPickerState()
+            }
+            !hasFavorites -> {
+                // Home app installed but no favorites selected.
+                val activityClass = component.getControlsUiController().get().resolveActivity()
+                return disabledPickerState(
+                    actionText = context.getString(R.string.controls_open_app),
+                    actionIntent =
+                        Intent().apply {
+                            component = ComponentName(context, activityClass)
+                            putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+                        },
+                )
+            }
         }
 
         return KeyguardQuickAffordanceConfig.PickerScreenState.Default()
@@ -172,6 +187,27 @@
         }
     }
 
+    private fun disabledPickerState(
+        actionText: String? = null,
+        actionIntent: Intent? = null,
+    ): KeyguardQuickAffordanceConfig.PickerScreenState.Disabled {
+        check(actionIntent == null || actionText != null)
+
+        return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+            instructions =
+                listOf(
+                    context.getString(
+                        R.string.keyguard_affordance_enablement_dialog_home_instruction_1
+                    ),
+                    context.getString(
+                        R.string.keyguard_affordance_enablement_dialog_home_instruction_2
+                    ),
+                ),
+            actionText = actionText,
+            actionIntent = actionIntent,
+        )
+    }
+
     companion object {
         private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 28dc5bd..42fe9bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,11 +18,13 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.app.AlertDialog
+import android.content.Context
 import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
@@ -80,9 +82,9 @@
 
         /**
          * The picker shows the item for selecting this affordance as disabled. Clicking on it will
-         * show the given instructions to the user. If [actionText] and [actionComponentName] are
-         * provided (optional) a button will be shown to open an activity to help the user complete
-         * the steps described in the instructions.
+         * show the given instructions to the user. If [actionText] and [actionIntent] are provided
+         * (optional) a button will be shown to open an activity to help the user complete the steps
+         * described in the instructions.
          */
         data class Disabled(
             /** List of human-readable instructions for setting up the quick affordance. */
@@ -93,24 +95,22 @@
              */
             val actionText: String? = null,
             /**
-             * Optional component name to be able to build an `Intent` that opens an `Activity` for
-             * the user to be able to set up the quick affordance and make it enabled.
-             *
-             * This is either just an action for the `Intent` or a package name and action,
-             * separated by
-             * [Contract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR] for
-             * convenience, you can use the [componentName] function.
+             * Optional [Intent] that opens an `Activity` for the user to be able to set up the
+             * quick affordance and make it enabled.
              */
-            val actionComponentName: String? = null,
+            val actionIntent: Intent? = null,
         ) : PickerScreenState() {
             init {
                 check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
                 check(
-                    (actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) ||
-                        (!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty())
+                    (actionText.isNullOrEmpty() && actionIntent == null) ||
+                        (!actionText.isNullOrEmpty() && actionIntent != null)
                 ) {
-                    "actionText and actionComponentName must either both be null/empty or both be" +
-                        " non-empty!"
+                    """
+                        actionText and actionIntent must either both be null/empty or both be
+                        non-null and non-empty!
+                    """
+                        .trimIndent()
                 }
             }
         }
@@ -163,17 +163,33 @@
     }
 
     companion object {
-        fun componentName(
-            packageName: String? = null,
-            action: String?,
-        ): String? {
-            return when {
-                action.isNullOrEmpty() -> null
-                !packageName.isNullOrEmpty() ->
-                    "$packageName${Contract.LockScreenQuickAffordances.AffordanceTable
-                        .COMPONENT_NAME_SEPARATOR}$action"
-                else -> action
+
+        /**
+         * Returns an [Intent] that can be used to start an activity that opens the app store app to
+         * a page showing the app with the passed-in [packageName].
+         *
+         * If the feature isn't enabled on this device/variant/configuration, a `null` will be
+         * returned.
+         */
+        fun appStoreIntent(context: Context, packageName: String?): Intent? {
+            if (packageName.isNullOrEmpty()) {
+                return null
+            }
+
+            val appStorePackageName = context.getString(R.string.config_appStorePackageName)
+            val linkTemplate = context.getString(R.string.config_appStoreAppLinkTemplate)
+            if (appStorePackageName.isEmpty() || linkTemplate.isEmpty()) {
+                return null
+            }
+
+            check(linkTemplate.contains(APP_PACKAGE_NAME_PLACEHOLDER))
+
+            return Intent(Intent.ACTION_VIEW).apply {
+                setPackage(appStorePackageName)
+                data = Uri.parse(linkTemplate.replace(APP_PACKAGE_NAME_PLACEHOLDER, packageName))
             }
         }
+
+        private const val APP_PACKAGE_NAME_PLACEHOLDER = "\$packageName"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 44e74e7..9db3c22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -63,7 +63,7 @@
                         val hasCards = response?.walletCards?.isNotEmpty() == true
                         trySendWithFailureLogging(
                             state(
-                                isFeatureEnabled = walletController.isWalletEnabled,
+                                isFeatureEnabled = isWalletAvailable(),
                                 hasCard = hasCards,
                                 tileIcon = walletController.walletClient.tileIcon,
                             ),
@@ -100,7 +100,7 @@
         return when {
             !walletController.walletClient.isWalletServiceAvailable ->
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
-            !walletController.isWalletEnabled || queryCards().isEmpty() -> {
+            !isWalletAvailable() || queryCards().isEmpty() -> {
                 KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
                     instructions =
                         listOf(
@@ -146,6 +146,11 @@
         }
     }
 
+    private fun isWalletAvailable() =
+        with(walletController.walletClient) {
+            isWalletServiceAvailable && isWalletFeatureAvailable
+        }
+
     private fun state(
         isFeatureEnabled: Boolean,
         hasCard: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 96c94d7..bd3b83c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.content.Context
+import android.content.Intent
 import android.os.UserHandle
 import android.util.LayoutDirection
 import com.android.systemui.Dumpable
@@ -176,8 +177,18 @@
                         pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
                     instructions = disabledPickerState?.instructions,
                     actionText = disabledPickerState?.actionText,
-                    actionComponentName = disabledPickerState?.actionComponentName,
-                    configureIntent = defaultPickerState?.configureIntent,
+                    actionIntent =
+                        disabledPickerState?.actionIntent?.apply {
+                            addFlags(
+                                Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+                            )
+                        },
+                    configureIntent =
+                        defaultPickerState?.configureIntent?.apply {
+                            addFlags(
+                                Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+                            )
+                        },
                 )
             }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index e7e9159..3c96eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -41,11 +41,8 @@
      */
     val actionText: String? = null,
 
-    /**
-     * If not enabled, an optional component name (package and action) for a button that takes the
-     * user to a destination where they can re-enable it.
-     */
-    val actionComponentName: String? = null,
+    /** Optional [Intent] to use to start an activity to re-enable this affordance. */
+    val actionIntent: Intent? = null,
 
     /** Optional [Intent] to use to start an activity to configure this affordance. */
     val configureIntent: Intent? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 516fbf5..14386c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -76,7 +76,6 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.widget.CachingIconView;
@@ -1211,24 +1210,24 @@
 
     private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
         return new TurbulenceNoiseAnimationConfig(
-                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+                /* gridCount= */ 2.14f,
                 TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
-                /* noiseMoveSpeedX= */ 0f,
+                /* noiseMoveSpeedX= */ 0.42f,
                 /* noiseMoveSpeedY= */ 0f,
                 TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
                 /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
-                // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
-                // Thus, set the background color with alpha 0.
-                /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
-                TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+                /* backgroundColor= */ Color.BLACK,
+                /* opacity= */ 51,
                 /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
                 /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
                 TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
-                /* easeInDuration= */ 2500f,
-                /* easeOutDuration= */ 2500f,
+                /* easeInDuration= */ 1350f,
+                /* easeOutDuration= */ 1350f,
                 this.getContext().getResources().getDisplayMetrics().density,
-                BlendMode.PLUS,
-                /* onAnimationEnd= */ null
+                BlendMode.SCREEN,
+                /* onAnimationEnd= */ null,
+                /* lumaMatteBlendFactor= */ 0.26f,
+                /* lumaMatteOverallBrightness= */ 0.09f
         );
     }
     private void clearButton(final ImageButton button) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 529b980..b4578e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -59,6 +59,17 @@
 public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
     private static final String TAG = "MediaOutputBroadcastDialog";
 
+    static final int METADATA_BROADCAST_NAME = 0;
+    static final int METADATA_BROADCAST_CODE = 1;
+
+    private static final int MAX_BROADCAST_INFO_UPDATE = 3;
+    @VisibleForTesting
+    static final int BROADCAST_CODE_MAX_LENGTH = 16;
+    @VisibleForTesting
+    static final int BROADCAST_CODE_MIN_LENGTH = 4;
+    @VisibleForTesting
+    static final int BROADCAST_NAME_MAX_LENGTH = 254;
+
     private ViewStub mBroadcastInfoArea;
     private ImageView mBroadcastQrCodeView;
     private ImageView mBroadcastNotify;
@@ -68,14 +79,16 @@
     private ImageView mBroadcastCodeEye;
     private Boolean mIsPasswordHide = true;
     private ImageView mBroadcastCodeEdit;
-    private AlertDialog mAlertDialog;
+    @VisibleForTesting
+    AlertDialog mAlertDialog;
     private TextView mBroadcastErrorMessage;
     private int mRetryCount = 0;
     private String mCurrentBroadcastName;
     private String mCurrentBroadcastCode;
     private boolean mIsStopbyUpdateBroadcastCode = false;
+    private boolean mIsLeBroadcastAssistantCallbackRegistered;
 
-    private TextWatcher mTextWatcher = new TextWatcher() {
+    private TextWatcher mBroadcastCodeTextWatcher = new TextWatcher() {
         @Override
         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
             // Do nothing
@@ -103,7 +116,9 @@
                         R.string.media_output_broadcast_code_hint_no_less_than_min);
             } else if (breakBroadcastCodeRuleTextLengthMoreThanMax) {
                 mBroadcastErrorMessage.setText(
-                        R.string.media_output_broadcast_code_hint_no_more_than_max);
+                        mContext.getResources().getString(
+                                R.string.media_output_broadcast_edit_hint_no_more_than_max,
+                                BROADCAST_CODE_MAX_LENGTH));
             }
 
             mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE);
@@ -114,7 +129,40 @@
         }
     };
 
-    private boolean mIsLeBroadcastAssistantCallbackRegistered;
+    private TextWatcher mBroadcastNameTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            // Do nothing
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            // Do nothing
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            if (mAlertDialog == null || mBroadcastErrorMessage == null) {
+                return;
+            }
+            boolean breakBroadcastNameRuleTextLengthMoreThanMax =
+                    s.length() > BROADCAST_NAME_MAX_LENGTH;
+            boolean breakRule = breakBroadcastNameRuleTextLengthMoreThanMax || (s.length() == 0);
+
+            if (breakBroadcastNameRuleTextLengthMoreThanMax) {
+                mBroadcastErrorMessage.setText(
+                        mContext.getResources().getString(
+                                R.string.media_output_broadcast_edit_hint_no_more_than_max,
+                                BROADCAST_NAME_MAX_LENGTH));
+            }
+            mBroadcastErrorMessage.setVisibility(
+                    breakBroadcastNameRuleTextLengthMoreThanMax ? View.VISIBLE : View.INVISIBLE);
+            Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+            if (positiveBtn != null) {
+                positiveBtn.setEnabled(breakRule ? false : true);
+            }
+        }
+    };
 
     private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
             new BluetoothLeBroadcastAssistant.Callback() {
@@ -187,13 +235,6 @@
                 }
             };
 
-    static final int METADATA_BROADCAST_NAME = 0;
-    static final int METADATA_BROADCAST_CODE = 1;
-
-    private static final int MAX_BROADCAST_INFO_UPDATE = 3;
-    private static final int BROADCAST_CODE_MAX_LENGTH = 16;
-    private static final int BROADCAST_CODE_MIN_LENGTH = 4;
-
     MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
             BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
         super(context, broadcastSender, mediaOutputController);
@@ -392,13 +433,12 @@
                 R.layout.media_output_broadcast_update_dialog, null);
         final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
         editText.setText(editString);
-        if (isBroadcastCode) {
-            editText.addTextChangedListener(mTextWatcher);
-        }
+        editText.addTextChangedListener(
+                isBroadcastCode ? mBroadcastCodeTextWatcher : mBroadcastNameTextWatcher);
         mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message);
         mAlertDialog = new Builder(mContext)
                 .setTitle(isBroadcastCode ? R.string.media_output_broadcast_code
-                    : R.string.media_output_broadcast_name)
+                        : R.string.media_output_broadcast_name)
                 .setView(layout)
                 .setNegativeButton(android.R.string.cancel, null)
                 .setPositiveButton(R.string.media_output_broadcast_dialog_save,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 0ce20cd..8da7cec 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -19,6 +19,7 @@
 import android.app.role.OnRoleHoldersChangedListener
 import android.app.role.RoleManager
 import android.content.Context
+import android.content.Intent
 import android.hardware.input.InputSettings
 import android.os.Build
 import android.os.UserHandle
@@ -42,7 +43,6 @@
 import com.android.systemui.notetask.NoteTaskEnabledKey
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskInfoResolver
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
 import com.android.systemui.stylus.StylusManager
 import dagger.Lazy
 import java.util.concurrent.Executor
@@ -130,8 +130,9 @@
                     context.getString(
                         R.string.keyguard_affordance_enablement_dialog_notes_app_action
                     ),
-                    "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
-                        "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE",
+                    Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
+                        setPackage(context.packageName)
+                    }
                 )
             }
             else -> PickerScreenState.UnavailableOnDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 897b0e7..5d02830 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -65,14 +65,12 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.DisplayTracker;
 
-import dagger.Lazy;
-
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.inject.Inject;
 
-
+import dagger.Lazy;
 
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index a066242..d7ae575 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,6 +20,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -45,6 +47,11 @@
     ): CurrentTilesInteractor
 
     @Binds
+    abstract fun provideInstalledTilesPackageRepository(
+        impl: InstalledTilesComponentRepositoryImpl
+    ): InstalledTilesComponentRepository
+
+    @Binds
     @IntoMap
     @ClassKey(PrototypeCoreStartable::class)
     abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
new file mode 100644
index 0000000..498f403
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.annotation.WorkerThread
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+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.util.kotlin.isComponentActuallyEnabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+interface InstalledTilesComponentRepository {
+
+    fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
+}
+
+@SysUISingleton
+class InstalledTilesComponentRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    private val packageManager: PackageManager,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : InstalledTilesComponentRepository {
+
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+        conflatedCallbackFlow {
+                val receiver =
+                    object : BroadcastReceiver() {
+                        override fun onReceive(context: Context?, intent: Intent?) {
+                            trySend(Unit)
+                        }
+                    }
+                applicationContext.registerReceiverAsUser(
+                    receiver,
+                    UserHandle.of(userId),
+                    INTENT_FILTER,
+                    /* broadcastPermission = */ null,
+                    /* scheduler = */ null
+                )
+
+                awaitClose { applicationContext.unregisterReceiver(receiver) }
+            }
+            .onStart { emit(Unit) }
+            .map { reloadComponents(userId) }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    @WorkerThread
+    private fun reloadComponents(userId: Int): Set<ComponentName> {
+        return packageManager
+            .queryIntentServicesAsUser(INTENT, FLAGS, userId)
+            .mapNotNull { it.serviceInfo }
+            .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
+            .filter { packageManager.isComponentActuallyEnabled(it) }
+            .mapTo(mutableSetOf()) { it.componentName }
+    }
+
+    companion object {
+        private val INTENT_FILTER =
+            IntentFilter().apply {
+                addAction(Intent.ACTION_PACKAGE_ADDED)
+                addAction(Intent.ACTION_PACKAGE_CHANGED)
+                addAction(Intent.ACTION_PACKAGE_REMOVED)
+                addAction(Intent.ACTION_PACKAGE_REPLACED)
+                addDataScheme("package")
+            }
+        private val INTENT = Intent(TileService.ACTION_QS_TILE)
+        private val FLAGS =
+            ResolveInfoFlags.of(
+                (PackageManager.GET_SERVICES or
+                        PackageManager.MATCH_DIRECT_BOOT_AWARE or
+                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
+                    .toLong()
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 3b2362f..a162d11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -42,6 +42,8 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 
 /** Repository that tracks the current tiles. */
@@ -104,6 +106,8 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : TileSpecRepository {
 
+    private val mutex = Mutex()
+
     private val retailModeTiles by lazy {
         resources
             .getString(R.string.quick_settings_tiles_retail_mode)
@@ -145,37 +149,40 @@
             .flowOn(backgroundDispatcher)
     }
 
-    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
-        if (tile == TileSpec.Invalid) {
-            return
-        }
-        val tilesList = loadTiles(userId).toMutableList()
-        if (tile !in tilesList) {
-            if (position < 0 || position >= tilesList.size) {
-                tilesList.add(tile)
-            } else {
-                tilesList.add(position, tile)
+    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
+        mutex.withLock {
+            if (tile == TileSpec.Invalid) {
+                return
             }
-            storeTiles(userId, tilesList)
+            val tilesList = loadTiles(userId).toMutableList()
+            if (tile !in tilesList) {
+                if (position < 0 || position >= tilesList.size) {
+                    tilesList.add(tile)
+                } else {
+                    tilesList.add(position, tile)
+                }
+                storeTiles(userId, tilesList)
+            }
         }
-    }
 
-    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
-        if (tiles.all { it == TileSpec.Invalid }) {
-            return
+    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
+        mutex.withLock {
+            if (tiles.all { it == TileSpec.Invalid }) {
+                return
+            }
+            val tilesList = loadTiles(userId).toMutableList()
+            if (tilesList.removeAll(tiles)) {
+                storeTiles(userId, tilesList.toList())
+            }
         }
-        val tilesList = loadTiles(userId).toMutableList()
-        if (tilesList.removeAll(tiles)) {
-            storeTiles(userId, tilesList.toList())
-        }
-    }
 
-    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
-        val filtered = tiles.filter { it != TileSpec.Invalid }
-        if (filtered.isNotEmpty()) {
-            storeTiles(userId, filtered)
+    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
+        mutex.withLock {
+            val filtered = tiles.filter { it != TileSpec.Invalid }
+            if (filtered.isNotEmpty()) {
+                storeTiles(userId, filtered)
+            }
         }
-    }
 
     private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
         return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index c579f5c..ff881f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.external.TileLifecycleManager
 import com.android.systemui.qs.external.TileServiceKey
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.domain.model.TileModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -52,6 +53,8 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
@@ -117,11 +120,13 @@
  * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
  * * [CustomTile]s will only be destroyed if the user changes.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CurrentTilesInteractorImpl
 @Inject
 constructor(
     private val tileSpecRepository: TileSpecRepository,
+    private val installedTilesComponentRepository: InstalledTilesComponentRepository,
     private val userRepository: UserRepository,
     private val customTileStatePersister: CustomTileStatePersister,
     private val tileFactory: QSFactory,
@@ -141,7 +146,7 @@
     override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
 
     // This variable should only be accessed inside the collect of `startTileCollection`.
-    private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+    private val specsToTiles = mutableMapOf<TileSpec, TileOrNotInstalled>()
 
     private val currentUser = MutableStateFlow(userTracker.userId)
     override val userId = currentUser.asStateFlow()
@@ -149,6 +154,20 @@
     private val _userContext = MutableStateFlow(userTracker.userContext)
     override val userContext = _userContext.asStateFlow()
 
+    private val userAndTiles =
+        currentUser
+            .flatMapLatest { userId ->
+                tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) }
+            }
+            .distinctUntilChanged()
+            .pairwise(UserAndTiles(-1, emptyList()))
+            .flowOn(backgroundDispatcher)
+
+    private val installedPackagesWithTiles =
+        currentUser.flatMapLatest {
+            installedTilesComponentRepository.getInstalledTilesComponents(it)
+        }
+
     init {
         if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
             startTileCollection()
@@ -158,68 +177,98 @@
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun startTileCollection() {
         scope.launch {
-            userRepository.selectedUserInfo
-                .flatMapLatest { user ->
+            launch {
+                userRepository.selectedUserInfo.collect { user ->
                     currentUser.value = user.id
                     _userContext.value = userTracker.userContext
-                    tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
                 }
-                .distinctUntilChanged()
-                .pairwise(-1 to emptyList())
-                .flowOn(backgroundDispatcher)
-                .collect { (old, new) ->
-                    val newTileList = new.second
-                    val userChanged = old.first != new.first
-                    val newUser = new.first
+            }
 
-                    // Destroy all tiles that are not in the new set
-                    specsToTiles
-                        .filter { it.key !in newTileList }
-                        .forEach { entry ->
-                            logger.logTileDestroyed(
-                                entry.key,
-                                if (userChanged) {
-                                    QSPipelineLogger.TileDestroyedReason
-                                        .TILE_NOT_PRESENT_IN_NEW_USER
-                                } else {
-                                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
-                                }
-                            )
-                            entry.value.destroy()
-                        }
-                    // MutableMap will keep the insertion order
-                    val newTileMap = mutableMapOf<TileSpec, QSTile>()
+            launch(backgroundDispatcher) {
+                userAndTiles
+                    .combine(installedPackagesWithTiles) { usersAndTiles, packages ->
+                        Data(
+                            usersAndTiles.previousValue,
+                            usersAndTiles.newValue,
+                            packages,
+                        )
+                    }
+                    .collectLatest {
+                        val newTileList = it.newData.tiles
+                        val userChanged = it.oldData.userId != it.newData.userId
+                        val newUser = it.newData.userId
+                        val components = it.installedComponents
 
-                    newTileList.forEach { tileSpec ->
-                        if (tileSpec !in newTileMap) {
-                            val newTile =
-                                if (tileSpec in specsToTiles) {
-                                    processExistingTile(
-                                        tileSpec,
-                                        specsToTiles.getValue(tileSpec),
-                                        userChanged,
-                                        newUser
-                                    )
-                                        ?: createTile(tileSpec)
+                        // Destroy all tiles that are not in the new set
+                        specsToTiles
+                            .filter {
+                                it.key !in newTileList && it.value is TileOrNotInstalled.Tile
+                            }
+                            .forEach { entry ->
+                                logger.logTileDestroyed(
+                                    entry.key,
+                                    if (userChanged) {
+                                        QSPipelineLogger.TileDestroyedReason
+                                            .TILE_NOT_PRESENT_IN_NEW_USER
+                                    } else {
+                                        QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+                                    }
+                                )
+                                (entry.value as TileOrNotInstalled.Tile).tile.destroy()
+                            }
+                        // MutableMap will keep the insertion order
+                        val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()
+
+                        newTileList.forEach { tileSpec ->
+                            if (tileSpec !in newTileMap) {
+                                if (
+                                    tileSpec is TileSpec.CustomTileSpec &&
+                                        tileSpec.componentName !in components
+                                ) {
+                                    newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
                                 } else {
-                                    createTile(tileSpec)
+                                    // Create tile here will never try to create a CustomTile that
+                                    // is not installed
+                                    val newTile =
+                                        if (tileSpec in specsToTiles) {
+                                            processExistingTile(
+                                                tileSpec,
+                                                specsToTiles.getValue(tileSpec),
+                                                userChanged,
+                                                newUser
+                                            )
+                                                ?: createTile(tileSpec)
+                                        } else {
+                                            createTile(tileSpec)
+                                        }
+                                    if (newTile != null) {
+                                        newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
+                                    }
                                 }
-                            if (newTile != null) {
-                                newTileMap[tileSpec] = newTile
                             }
                         }
-                    }
 
-                    val resolvedSpecs = newTileMap.keys.toList()
-                    specsToTiles.clear()
-                    specsToTiles.putAll(newTileMap)
-                    _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
-                    if (resolvedSpecs != newTileList) {
-                        // There were some tiles that couldn't be created. Change the value in the
-                        // repository
-                        launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+                        val resolvedSpecs = newTileMap.keys.toList()
+                        specsToTiles.clear()
+                        specsToTiles.putAll(newTileMap)
+                        _currentSpecsAndTiles.value =
+                            newTileMap
+                                .filter { it.value is TileOrNotInstalled.Tile }
+                                .map {
+                                    TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
+                                }
+                        logger.logTilesNotInstalled(
+                            newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
+                            newUser
+                        )
+                        if (resolvedSpecs != newTileList) {
+                            // There were some tiles that couldn't be created. Change the value in
+                            // the
+                            // repository
+                            launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+                        }
                     }
-                }
+            }
         }
     }
 
@@ -301,42 +350,66 @@
 
     private fun processExistingTile(
         tileSpec: TileSpec,
-        qsTile: QSTile,
+        tileOrNotInstalled: TileOrNotInstalled,
         userChanged: Boolean,
         user: Int,
     ): QSTile? {
-        return when {
-            !qsTile.isAvailable -> {
-                logger.logTileDestroyed(
-                    tileSpec,
-                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
-                )
-                qsTile.destroy()
-                null
-            }
-            // Tile is in the current list of tiles and available.
-            // We have a handful of different cases
-            qsTile !is CustomTile -> {
-                // The tile is not a custom tile. Make sure they are reset to the correct user
-                if (userChanged) {
-                    qsTile.userSwitch(user)
-                    logger.logTileUserChanged(tileSpec, user)
+        return when (tileOrNotInstalled) {
+            is TileOrNotInstalled.NotInstalled -> null
+            is TileOrNotInstalled.Tile -> {
+                val qsTile = tileOrNotInstalled.tile
+                when {
+                    !qsTile.isAvailable -> {
+                        logger.logTileDestroyed(
+                            tileSpec,
+                            QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+                        )
+                        qsTile.destroy()
+                        null
+                    }
+                    // Tile is in the current list of tiles and available.
+                    // We have a handful of different cases
+                    qsTile !is CustomTile -> {
+                        // The tile is not a custom tile. Make sure they are reset to the correct
+                        // user
+                        if (userChanged) {
+                            qsTile.userSwitch(user)
+                            logger.logTileUserChanged(tileSpec, user)
+                        }
+                        qsTile
+                    }
+                    qsTile.user == user -> {
+                        // The tile is a custom tile for the same user, just return it
+                        qsTile
+                    }
+                    else -> {
+                        // The tile is a custom tile and the user has changed. Destroy it
+                        qsTile.destroy()
+                        logger.logTileDestroyed(
+                            tileSpec,
+                            QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+                        )
+                        null
+                    }
                 }
-                qsTile
-            }
-            qsTile.user == user -> {
-                // The tile is a custom tile for the same user, just return it
-                qsTile
-            }
-            else -> {
-                // The tile is a custom tile and the user has changed. Destroy it
-                qsTile.destroy()
-                logger.logTileDestroyed(
-                    tileSpec,
-                    QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
-                )
-                null
             }
         }
     }
+
+    private sealed interface TileOrNotInstalled {
+        object NotInstalled : TileOrNotInstalled
+
+        @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled
+    }
+
+    private data class UserAndTiles(
+        val userId: Int,
+        val tiles: List<TileSpec>,
+    )
+
+    private data class Data(
+        val oldData: UserAndTiles,
+        val newData: UserAndTiles,
+        val installedComponents: Set<ComponentName>,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index ff7d206..8318ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -124,6 +124,18 @@
         tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
     }
 
+    fun logTilesNotInstalled(tiles: Collection<TileSpec>, user: Int) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = tiles.toString()
+                int1 = user
+            },
+            { "Tiles kept for not installed packages for user $int1: $str1" }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index f080d3d..3af75ce 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,6 +34,7 @@
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.doOnLayout
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
@@ -220,6 +221,7 @@
             override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
             override fun dispatchDemoCommand(command: String, args: Bundle) =
                 clock.dispatchDemoCommand(command, args)
+
             override fun onDemoModeStarted() = clock.onDemoModeStarted()
             override fun onDemoModeFinished() = clock.onDemoModeFinished()
         }
@@ -259,6 +261,7 @@
                     resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
                 lastInsets?.let { updateConstraintsForInsets(header, it) }
                 updateResources()
+                updateCarrierGroupPadding()
             }
         }
 
@@ -291,6 +294,7 @@
         privacyIconsController.chipVisibilityListener = chipVisibilityListener
         updateVisibility()
         updateTransition()
+        updateCarrierGroupPadding()
 
         header.setOnApplyWindowInsetsListener(insetListener)
 
@@ -298,8 +302,6 @@
             val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
             v.pivotX = newPivot
             v.pivotY = v.height.toFloat() / 2
-
-            mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
         }
         clock.setOnClickListener { launchClockActivity() }
 
@@ -359,6 +361,14 @@
             .load(context, resources.getXml(R.xml.large_screen_shade_header))
     }
 
+    private fun updateCarrierGroupPadding() {
+        clock.doOnLayout {
+            val maxClockWidth =
+                (clock.width * resources.getFloat(R.dimen.qqs_expand_clock_scale)).toInt()
+            mShadeCarrierGroup.setPaddingRelative(maxClockWidth, 0, 0, 0)
+        }
+    }
+
     private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
         val cutout = insets.displayCutout.also { this.cutout = it }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
new file mode 100644
index 0000000..891ee0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.annotation.WorkerThread
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import com.android.systemui.util.Assert
+
+@WorkerThread
+fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
+    Assert.isNotMainThread()
+    return when (getComponentEnabledSetting(componentInfo.componentName)) {
+        PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+        PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+        PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> componentInfo.isEnabled
+        else -> false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b24a692..b848d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2006,14 +2006,14 @@
                 if (row.anim == null) {
                     row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
                     row.anim.setInterpolator(new DecelerateInterpolator());
+                    row.anim.addListener(
+                        getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
                 } else {
                     row.anim.cancel();
                     row.anim.setIntValues(progress, newProgress);
                 }
                 row.animTargetProgress = newProgress;
                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
-                row.anim.addListener(
-                        getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
                 row.anim.start();
             } else {
                 // update slider directly to clamped value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
index 7fb088e..bd4e8da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -27,16 +28,20 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ControlsTileResourceConfigurationImplTest : SysuiTestCase() {
+
+    @Test
+    fun getPackageName() {
+        assertThat(ControlsTileResourceConfigurationImpl().getPackageName()).isNull()
+    }
+
     @Test
     fun getTileImageId() {
         val instance = ControlsTileResourceConfigurationImpl()
-        assertEquals(instance.getTileImageId(),
-            R.drawable.controls_icon)
+        assertEquals(instance.getTileImageId(), R.drawable.controls_icon)
     }
     @Test
     fun getTileTitleId() {
         val instance = ControlsTileResourceConfigurationImpl()
-        assertEquals(instance.getTileTitleId(),
-            R.string.quick_controls_title)
+        assertEquals(instance.getTileTitleId(), R.string.quick_controls_title)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 9144b13..0b27bc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -22,10 +22,10 @@
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,32 +40,25 @@
 import org.mockito.Answers
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ControlsComponentTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var controller: ControlsController
-    @Mock
-    private lateinit var uiController: ControlsUiController
-    @Mock
-    private lateinit var listingController: ControlsListingController
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var controller: ControlsController
+    @Mock private lateinit var uiController: ControlsUiController
+    @Mock private lateinit var listingController: ControlsListingController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var userTracker: UserTracker
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock
     private lateinit var optionalControlsTileResourceConfiguration:
-            Optional<ControlsTileResourceConfiguration>
-    @Mock
-    private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
+        Optional<ControlsTileResourceConfiguration>
+    @Mock private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
 
     private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
@@ -123,8 +116,7 @@
 
     @Test
     fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() {
-        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
-            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED)
         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
         controlsSettingsRepository.setCanShowControlsInLockscreen(false)
         val component = setupComponent(true)
@@ -134,8 +126,7 @@
 
     @Test
     fun testFeatureEnabledAndCanShowOnLockScreenVisibility() {
-        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
-            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED)
         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
         controlsSettingsRepository.setCanShowControlsInLockscreen(true)
         val component = setupComponent(true)
@@ -146,8 +137,7 @@
     @Test
     fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
         controlsSettingsRepository.setCanShowControlsInLockscreen(false)
-        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
-            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED)
         `when`(keyguardStateController.isUnlocked()).thenReturn(true)
         val component = setupComponent(true)
 
@@ -158,8 +148,7 @@
     fun testGetTileImageId() {
         val tileImageId = 0
 
-        `when`(controlsTileResourceConfiguration.getTileImageId())
-            .thenReturn(tileImageId)
+        `when`(controlsTileResourceConfiguration.getTileImageId()).thenReturn(tileImageId)
         val component = setupComponent(true)
         assertEquals(component.getTileImageId(), tileImageId)
     }
@@ -168,12 +157,19 @@
     fun testGetTileTitleId() {
         val tileTitleId = 0
 
-        `when`(controlsTileResourceConfiguration.getTileTitleId())
-            .thenReturn(tileTitleId)
+        `when`(controlsTileResourceConfiguration.getTileTitleId()).thenReturn(tileTitleId)
         val component = setupComponent(true)
         assertEquals(component.getTileTitleId(), tileTitleId)
     }
 
+    @Test
+    fun getPackageName() {
+        val packageName = "packageName"
+        `when`(controlsTileResourceConfiguration.getPackageName()).thenReturn(packageName)
+        val component = setupComponent(true)
+        assertEquals(component.getPackageName(), packageName)
+    }
+
     private fun setupComponent(enabled: Boolean): ControlsComponent {
         return ControlsComponent(
             enabled,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index f8cb408..1815ea9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.app.Activity
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -24,6 +25,7 @@
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -75,6 +77,7 @@
     @Mock private lateinit var component: ControlsComponent
     @Mock private lateinit var controlsController: ControlsController
     @Mock private lateinit var controlsListingController: ControlsListingController
+    @Mock private lateinit var controlsUiController: ControlsUiController
     @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo
     @Captor
     private lateinit var callbackCaptor:
@@ -98,6 +101,8 @@
         whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
         whenever(component.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
+        whenever(controlsUiController.resolveActivity()).thenReturn(FakeActivity::class.java)
+        whenever(component.getControlsUiController()).thenReturn(Optional.of(controlsUiController))
         if (hasPanels) {
             whenever(controlsServiceInfo.panelActivity).thenReturn(mock())
         }
@@ -178,4 +183,6 @@
             )
         job.cancel()
     }
+
+    private class FakeActivity : Activity()
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..7941a23
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Test
+    fun appStoreIntent() {
+        overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+        overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName")
+        val packageName = "com.app.package.name"
+
+        val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+
+        assertThat(intent).isNotNull()
+        assertThat(intent?.`package`).isEqualTo("app.store.package.name")
+        assertThat(intent?.action).isEqualTo(Intent.ACTION_VIEW)
+        assertThat(intent?.data).isEqualTo(Uri.parse("prefix?id=$packageName"))
+    }
+
+    @Test
+    fun appStoreIntent_packageNameNotConfigured_returnNull() {
+        overrideResource(R.string.config_appStorePackageName, "")
+        overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName")
+        val packageName = "com.app.package.name"
+
+        val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+
+        assertThat(intent).isNull()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun appStoreIntent_packageNameMisconfigured_throwsIllegalStateException() {
+        overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+        overrideResource(
+            R.string.config_appStoreAppLinkTemplate,
+            "prefix?id=\$misconfiguredPackageName"
+        )
+        val packageName = "com.app.package.name"
+
+        KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+    }
+
+    @Test
+    fun appStoreIntent_linkTemplateNotConfigured_returnNull() {
+        overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+        overrideResource(R.string.config_appStoreAppLinkTemplate, "")
+        val packageName = "com.app.package.name"
+
+        val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+
+        assertThat(intent).isNull()
+    }
+
+    @Test
+    fun appStoreIntent_appPackageNameNull_returnNull() {
+        overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+        overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName")
+
+        val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, null)
+
+        assertThat(intent).isNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 111b8e8..d36e778 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -92,8 +92,8 @@
     }
 
     @Test
-    fun affordance_walletNotEnabled_modelIsNone() = runBlockingTest {
-        setUpState(isWalletEnabled = false)
+    fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
+        setUpState(isWalletFeatureAvailable = false)
         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +165,7 @@
     @Test
     fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
         setUpState(
-            isWalletEnabled = false,
+            isWalletFeatureAvailable = false,
         )
 
         assertThat(underTest.getPickerScreenState())
@@ -183,16 +183,15 @@
     }
 
     private fun setUpState(
-        isWalletEnabled: Boolean = true,
+        isWalletFeatureAvailable: Boolean = true,
         isWalletServiceAvailable: Boolean = true,
         isWalletQuerySuccessful: Boolean = true,
         hasSelectedCard: Boolean = true,
     ) {
-        whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
-
         val walletClient: QuickAccessWalletClient = mock()
         whenever(walletClient.tileIcon).thenReturn(ICON)
         whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+        whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable)
 
         whenever(walletController.walletClient).thenReturn(walletClient)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 205fa0f..9dba9b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -35,6 +35,8 @@
 import android.os.PowerExemptionManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -58,6 +60,8 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
+import com.google.common.base.Strings;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -252,4 +256,106 @@
         mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
         assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
     }
+
+    @Test
+    public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() {
+        ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.broadcast_name_edit);
+        TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+                R.id.broadcast_name_summary);
+        broadcastName.setText(BROADCAST_NAME_TEST);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        broadcastNameEdit.callOnClick();
+        EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_edit_text);
+        TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_error_message);
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+        // input the invalid text
+        String moreThanMax = Strings.repeat("a",
+                MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3);
+        editText.setText(moreThanMax);
+
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() {
+        ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.broadcast_name_edit);
+        TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+                R.id.broadcast_name_summary);
+        broadcastName.setText(BROADCAST_NAME_TEST);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        broadcastNameEdit.callOnClick();
+        EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_edit_text);
+        TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_error_message);
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+        // input the invalid text
+        String testString = Strings.repeat("a",
+                MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2);
+        editText.setText(testString);
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+
+        // input the valid text
+        testString = Strings.repeat("b",
+                MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100);
+        editText.setText(testString);
+
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() {
+        ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.broadcast_code_edit);
+        TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+                R.id.broadcast_code_summary);
+        broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        broadcastCodeEdit.callOnClick();
+        EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_edit_text);
+        TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_error_message);
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+        // input the invalid text
+        String moreThanMax = Strings.repeat("a",
+                MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1);
+        editText.setText(moreThanMax);
+
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() {
+        ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.broadcast_code_edit);
+        TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+                R.id.broadcast_code_summary);
+        broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        broadcastCodeEdit.callOnClick();
+        EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_edit_text);
+        TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+                R.id.broadcast_error_message);
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+        // input the invalid text
+        String moreThanMax = Strings.repeat("a",
+                MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1);
+        editText.setText(moreThanMax);
+
+        assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index 4526580..cc64cc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.notetask.NoteTaskInfoResolver
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
 import com.android.systemui.stylus.StylusManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -283,7 +282,7 @@
     }
 
     @Test
-    fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest {
+    fun getPickerScreenState_noDefaultNoteAppSet_shouldReturnDisabled() = runTest {
         val underTest = createUnderTest(isEnabled = true)
         whenever(
                 roleManager.getRoleHoldersAsUser(
@@ -293,16 +292,16 @@
             )
             .thenReturn(emptyList())
 
-        assertThat(underTest.getPickerScreenState())
-            .isEqualTo(
-                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
-                    listOf("Select a default notes app to use the notetaking shortcut"),
-                    actionText = "Select app",
-                    actionComponentName =
-                        "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
-                            "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
-                )
-            )
+        val pickerScreenState = underTest.getPickerScreenState()
+        assertThat(pickerScreenState is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled)
+            .isTrue()
+        val disabled = pickerScreenState as KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
+        assertThat(disabled.instructions)
+            .isEqualTo(listOf("Select a default notes app to use the notetaking shortcut"))
+        assertThat(disabled.actionText).isEqualTo("Select app")
+        assertThat(disabled.actionIntent?.action)
+            .isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)
+        assertThat(disabled.actionIntent?.`package`).isEqualTo(context.packageName)
     }
     // endregion
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
new file mode 100644
index 0000000..18f3837
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -0,0 +1,278 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+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.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var packageManager: PackageManager
+    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+
+    private lateinit var underTest: InstalledTilesComponentRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        // Use the default value set in the ServiceInfo
+        whenever(packageManager.getComponentEnabledSetting(any()))
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+        // Return empty by default
+        whenever(packageManager.queryIntentServicesAsUser(any(), any<ResolveInfoFlags>(), anyInt()))
+            .thenReturn(emptyList())
+
+        underTest =
+            InstalledTilesComponentRepositoryImpl(
+                context,
+                packageManager,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun registersAndUnregistersBroadcastReceiver() =
+        testScope.runTest {
+            val user = 10
+            val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
+            runCurrent()
+
+            verify(context)
+                .registerReceiverAsUser(
+                    capture(receiverCaptor),
+                    eq(UserHandle.of(user)),
+                    any(),
+                    nullable(),
+                    nullable(),
+                )
+
+            verify(context, never()).unregisterReceiver(receiverCaptor.value)
+
+            job.cancel()
+            runCurrent()
+            verify(context).unregisterReceiver(receiverCaptor.value)
+        }
+
+    @Test
+    fun intentFilterForCorrectActionsAndScheme() =
+        testScope.runTest {
+            val filterCaptor = argumentCaptor<IntentFilter>()
+
+            backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
+            runCurrent()
+
+            verify(context)
+                .registerReceiverAsUser(
+                    any(),
+                    any(),
+                    capture(filterCaptor),
+                    nullable(),
+                    nullable(),
+                )
+
+            with(filterCaptor.value) {
+                assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
+                assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
+                assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
+                assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
+                assertThat(countActions()).isEqualTo(4)
+
+                assertThat(hasDataScheme("package")).isTrue()
+                assertThat(countDataSchemes()).isEqualTo(1)
+            }
+        }
+
+    @Test
+    fun componentsLoadedOnStart() =
+        testScope.runTest {
+            val userId = 0
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+            assertThat(componentNames).containsExactly(TEST_COMPONENT)
+        }
+
+    @Test
+    fun componentAdded_foundAfterBroadcast() =
+        testScope.runTest {
+            val userId = 0
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            assertThat(componentNames).isEmpty()
+
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+            getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
+
+            assertThat(componentNames).containsExactly(TEST_COMPONENT)
+        }
+
+    @Test
+    fun componentWithoutPermission_notValid() =
+        testScope.runTest {
+            val userId = 0
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = false, defaultEnabled = true)
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            assertThat(componentNames).isEmpty()
+        }
+
+    @Test
+    fun componentNotEnabled_notValid() =
+        testScope.runTest {
+            val userId = 0
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = false)
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            assertThat(componentNames).isEmpty()
+        }
+
+    private fun getRegisteredReceiver(): BroadcastReceiver {
+        verify(context)
+            .registerReceiverAsUser(
+                capture(receiverCaptor),
+                any(),
+                any(),
+                nullable(),
+                nullable(),
+            )
+
+        return receiverCaptor.value
+    }
+
+    companion object {
+        private val INTENT = Intent(TileService.ACTION_QS_TILE)
+        private val FLAGS =
+            ResolveInfoFlags.of(
+                (PackageManager.MATCH_DIRECT_BOOT_AWARE or
+                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+                        PackageManager.GET_SERVICES)
+                    .toLong()
+            )
+        private val PERMISSION = BIND_QUICK_SETTINGS_TILE
+
+        private val TEST_COMPONENT = ComponentName("pkg", "cls")
+
+        private fun matchFlags() =
+            argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value }
+        private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action }
+
+        private fun ResolveInfo(
+            componentName: ComponentName,
+            hasPermission: Boolean,
+            defaultEnabled: Boolean
+        ): ResolveInfo {
+            val applicationInfo = ApplicationInfo().apply { enabled = true }
+            val serviceInfo =
+                ServiceInfo().apply {
+                    packageName = componentName.packageName
+                    name = componentName.className
+                    if (hasPermission) {
+                        permission = PERMISSION
+                    }
+                    enabled = defaultEnabled
+                    this.applicationInfo = applicationInfo
+                }
+            val resolveInfo = ResolveInfo()
+            resolveInfo.serviceInfo = serviceInfo
+            return resolveInfo
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 426ff67..e7ad489 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.qs.external.TileServiceKey
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.domain.model.TileModel
@@ -73,6 +74,7 @@
 
     private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
     private val userRepository = FakeUserRepository()
+    private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
     private val tileFactory = FakeQSFactory(::tileCreator)
     private val customTileAddedRepository: CustomTileAddedRepository =
         FakeCustomTileAddedRepository()
@@ -100,11 +102,13 @@
         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+
         setUserTracker(0)
 
         underTest =
             CurrentTilesInteractorImpl(
                 tileSpecRepository = tileSpecRepository,
+                installedTilesComponentRepository = installedTilesPackageRepository,
                 userRepository = userRepository,
                 customTileStatePersister = customTileStatePersister,
                 tileFactory = tileFactory,
@@ -609,6 +613,40 @@
             assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
         }
 
+    @Test
+    fun packageNotInstalled_customTileNotVisible() =
+        testScope.runTest(USER_INFO_0) {
+            installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            assertThat(tiles!!.size).isEqualTo(1)
+            assertThat(tiles!![0].spec).isEqualTo(specs[0])
+        }
+
+    @Test
+    fun packageInstalledLater_customTileAdded() =
+        testScope.runTest(USER_INFO_0) {
+            installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+            val tiles by collectLastValue(underTest.currentTiles)
+            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            assertThat(tiles!!.size).isEqualTo(2)
+
+            installedTilesPackageRepository.setInstalledPackagesForUser(
+                USER_INFO_0.id,
+                setOf(TEST_COMPONENT)
+            )
+
+            assertThat(tiles!!.size).isEqualTo(3)
+            assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
+        }
+
     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
         this.state = state
         this.label = label
@@ -654,6 +692,7 @@
 
     private suspend fun switchUser(user: UserInfo) {
         setUserTracker(user.id)
+        installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
         userRepository.setSelectedUserInfo(user)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 20da8a6..2da2e92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -78,6 +78,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
@@ -387,7 +388,7 @@
         whenever(clock.isLayoutRtl).thenReturn(false)
 
         val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
-        verify(clock).addOnLayoutChangeListener(capture(captor))
+        verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
 
         captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
         verify(clock).pivotX = 0f
@@ -400,7 +401,7 @@
         whenever(clock.isLayoutRtl).thenReturn(true)
 
         val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
-        verify(clock).addOnLayoutChangeListener(capture(captor))
+        verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
 
         captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
         verify(clock).pivotX = width.toFloat()
@@ -793,7 +794,7 @@
     @Test
     fun clockPivotYInCenter() {
         val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
-        verify(clock).addOnLayoutChangeListener(capture(captor))
+        verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
         var height = 100
         val width = 50
 
@@ -825,16 +826,17 @@
     }
 
     @Test
-    fun carrierLeftPaddingIsSetWhenClockLayoutChanges() {
-        val width = 200
-        whenever(clock.width).thenReturn(width)
-        whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml
+    fun carrierStartPaddingIsSetOnClockLayout() {
+        val clockWidth = 200
+        val maxClockScale = context.resources.getFloat(R.dimen.qqs_expand_clock_scale)
+        val expectedStartPadding = (clockWidth * maxClockScale).toInt()
+        whenever(clock.width).thenReturn(clockWidth)
+
         val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+        verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
+        captor.allValues.forEach { clock.executeLayoutChange(0, 0, clockWidth, 0, it) }
 
-        verify(clock).addOnLayoutChangeListener(capture(captor))
-        captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0)
-
-        verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
+        verify(carrierGroup).setPaddingRelative(expectedStartPadding, 0, 0, 0)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
new file mode 100644
index 0000000..2013bb0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.content.ComponentName
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) :
+    SysuiTestCase() {
+
+    @Mock private lateinit var packageManager: PackageManager
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun testComponentActuallyEnabled() {
+        whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+            .thenReturn(testCase.componentEnabledSetting)
+        val componentInfo =
+            mock<ComponentInfo>() {
+                whenever(isEnabled).thenReturn(testCase.componentIsEnabled)
+                whenever(componentName).thenReturn(TEST_COMPONENT)
+            }
+
+        assertThat(packageManager.isComponentActuallyEnabled(componentInfo))
+            .isEqualTo(testCase.expected)
+    }
+
+    internal data class TestCase(
+        @PackageManager.EnabledState val componentEnabledSetting: Int,
+        val componentIsEnabled: Boolean,
+        val expected: Boolean,
+    ) {
+        override fun toString(): String {
+            return "WHEN(" +
+                "componentIsEnabled = $componentIsEnabled, " +
+                "componentEnabledSetting = ${enabledStateToString()}) then " +
+                "EXPECTED = $expected"
+        }
+
+        private fun enabledStateToString() =
+            when (componentEnabledSetting) {
+                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> "STATE_DEFAULT"
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> "STATE_DISABLED"
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED -> {
+                    "STATE_DISABLED_UNTIL_USED"
+                }
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> "STATE_DISABLED_USER"
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> "STATE_ENABLED"
+                else -> "INVALID STATE"
+            }
+    }
+
+    companion object {
+        @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+        private val testDataComponentIsEnabled =
+            listOf(
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    componentIsEnabled = true,
+                    expected = true,
+                ),
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+                    componentIsEnabled = true,
+                    expected = false,
+                ),
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    componentIsEnabled = true,
+                    expected = false,
+                ),
+                TestCase(
+                    componentEnabledSetting =
+                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                    componentIsEnabled = true,
+                    expected = false,
+                ),
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                    componentIsEnabled = true,
+                    expected = true,
+                ),
+            )
+
+        private val testDataComponentIsDisabled =
+            listOf(
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    componentIsEnabled = false,
+                    expected = true,
+                ),
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+                    componentIsEnabled = false,
+                    expected = false,
+                ),
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    componentIsEnabled = false,
+                    expected = false,
+                ),
+                TestCase(
+                    componentEnabledSetting =
+                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                    componentIsEnabled = false,
+                    expected = false,
+                ),
+                TestCase(
+                    componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                    componentIsEnabled = false,
+                    expected = false,
+                ),
+            )
+
+        private val testData = testDataComponentIsDisabled + testDataComponentIsEnabled
+
+        private val TEST_COMPONENT = ComponentName("pkg", "cls")
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
new file mode 100644
index 0000000..ff6b7d0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository {
+
+    private val installedComponentsPerUser =
+        mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>()
+
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+        return getFlow(userId).asStateFlow()
+    }
+
+    fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) {
+        getFlow(userId).value = components
+    }
+
+    private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> =
+        installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ede461..6f20563 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2156,9 +2156,7 @@
                         }
                     }
 
-                    final boolean fgsTypeChangingFromShortFgs = r.isForeground && isOldTypeShortFgs;
-
-                    if (fgsTypeChangingFromShortFgs) {
+                    if (r.isForeground && isOldTypeShortFgs) {
 
                         // If we get here, that means startForeground(SHORT_SERVICE) is called again
                         // on a SHORT_SERVICE FGS.
@@ -2211,9 +2209,7 @@
                                 // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
                             }
                         }
-                    }
-
-                    if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount == 0) {
+                    } else if (r.mStartForegroundCount == 0) {
                         /*
                         If the service was started with startService(), not
                         startForegroundService(), and if startForeground() isn't called within
@@ -2244,7 +2240,7 @@
                                 r.mLoggedInfoAllowStartForeground = false;
                             }
                         }
-                    } else if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount >= 1) {
+                    } else if (r.mStartForegroundCount >= 1) {
                         // We get here if startForeground() is called multiple times
                         // on the same service after it's created, regardless of whether
                         // stopForeground() has been called or not.
@@ -7436,14 +7432,10 @@
             r.mAllowWhileInUsePermissionInFgs = true;
         }
 
-        // Either (or both) mAllowWhileInUsePermissionInFgs or mAllowStartForeground is
-        // newly allowed?
-        boolean newlyAllowed = false;
         if (!r.mAllowWhileInUsePermissionInFgs
                 || (r.mAllowStartForeground == REASON_DENIED)) {
             @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
-                    callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
-                    isBindService);
+                    callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
             // We store them to compare the old and new while-in-use logics to each other.
             // (They're not used for any other purposes.)
             if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -7477,7 +7469,7 @@
         }
         final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                 callingPackage, callingPid, callingUid, null /* targetProcess */,
-                BackgroundStartPrivileges.NONE, false);
+                BackgroundStartPrivileges.NONE);
         @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
                 allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
                 BackgroundStartPrivileges.NONE);
@@ -7501,19 +7493,15 @@
      */
     private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
             int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
-            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+            BackgroundStartPrivileges backgroundStartPrivileges) {
         int ret = REASON_DENIED;
 
-        final boolean forStartForeground = !isBindService;
-
-        if (forStartForeground) {
-            final int uidState = mAm.getUidStateLocked(callingUid);
-            if (ret == REASON_DENIED) {
-                // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
-                // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
-                if (uidState <= PROCESS_STATE_TOP) {
-                    ret = getReasonCodeFromProcState(uidState);
-                }
+        final int uidState = mAm.getUidStateLocked(callingUid);
+        if (ret == REASON_DENIED) {
+            // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
+            // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
+            if (uidState <= PROCESS_STATE_TOP) {
+                ret = getReasonCodeFromProcState(uidState);
             }
         }
 
@@ -7734,7 +7722,7 @@
                                         shouldAllowFgsWhileInUsePermissionLocked(
                                                 clientPackageName,
                                                 clientPid, clientUid, null /* targetProcess */,
-                                                BackgroundStartPrivileges.NONE, false);
+                                                BackgroundStartPrivileges.NONE);
                                 final @ReasonCode int allowStartFgs =
                                         shouldAllowFgsStartForegroundNoBindingCheckLocked(
                                                 allowWhileInUse2,
@@ -8163,7 +8151,7 @@
             String callingPackage) {
         return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
                 /* targetProcess */ null,
-                BackgroundStartPrivileges.NONE, false)
+                BackgroundStartPrivileges.NONE)
                 != REASON_DENIED;
     }
 
@@ -8171,7 +8159,7 @@
             String callingPackage, @Nullable ProcessRecord targetProcess,
             @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
         return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
-                targetProcess, backgroundStartPrivileges, false) != REASON_DENIED;
+                targetProcess, backgroundStartPrivileges) != REASON_DENIED;
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0b5b1cb..4b6d324 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
+import android.app.BroadcastOptions;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.os.SystemClock;
@@ -257,7 +258,10 @@
             deferredStatesApplyConsumer.accept(record, recordIndex);
         }
 
-        if (record.isReplacePending()) {
+        // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
+        // BroadcastOptions delivery group APIs.
+        if (record.isReplacePending()
+                && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
             final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
             if (replacedBroadcastRecord != null) {
                 return replacedBroadcastRecord;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index f6004d7..8688f25 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -269,7 +269,7 @@
                                 Activity.RESULT_CANCELED, null, null,
                                 false, false, oldRecord.shareIdentity, oldRecord.userId,
                                 oldRecord.callingUid, r.callingUid, r.callerPackage,
-                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
+                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failure ["
                                 + mQueueName + "] sending broadcast result of "
@@ -339,7 +339,7 @@
     private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
             BroadcastRecord r, String typeForLogging) {
         final Intent intent = r.intent;
-        for (int i = queue.size() - 1; i > 0; i--) {
+        for (int i = queue.size() - 1; i >= 0; i--) {
             final BroadcastRecord old = queue.get(i);
             if (old.userId == r.userId && intent.filterEquals(old.intent)) {
                 if (DEBUG_BROADCAST) {
@@ -617,7 +617,10 @@
                     r.curApp.info.packageName,
                     r.callerPackage,
                     r.calculateTypeForLogging(),
-                    r.getDeliveryGroupPolicy());
+                    r.getDeliveryGroupPolicy(),
+                    r.intent.getFlags(),
+                    BroadcastRecord.getReceiverPriority(curReceiver),
+                    r.callerProcState);
         }
         if (state == BroadcastRecord.IDLE) {
             Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -748,7 +751,7 @@
             Intent intent, int resultCode, String data, Bundle extras,
             boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
             int receiverUid, int callingUid, String callingPackage,
-            long dispatchDelay, long receiveDelay) throws RemoteException {
+            long dispatchDelay, long receiveDelay, int priority) throws RemoteException {
         // If the broadcaster opted-in to sharing their identity, then expose package visibility for
         // the receiver.
         if (shareIdentity) {
@@ -798,7 +801,8 @@
                     dispatchDelay, receiveDelay, 0 /* finish_delay */,
                     SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
                     app != null ? app.info.packageName : null, callingPackage,
-                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
+                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
+                    priority, r.callerProcState);
         }
     }
 
@@ -879,7 +883,7 @@
                         r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
                         filter.receiverList.uid, r.callingUid, r.callerPackage,
                         r.dispatchTime - r.enqueueTime,
-                        r.receiverTime - r.dispatchTime);
+                        r.receiverTime - r.dispatchTime, filter.getPriority());
                 // parallel broadcasts are fire-and-forget, not bookended by a call to
                 // finishReceiverLocked(), so we manage their activity-start token here
                 if (filter.receiverList.app != null
@@ -1170,7 +1174,7 @@
                                     r.resultData, r.resultExtras, false, false, r.shareIdentity,
                                     r.userId, r.callingUid, r.callingUid, r.callerPackage,
                                     r.dispatchTime - r.enqueueTime,
-                                    now - r.dispatchTime);
+                                    now - r.dispatchTime, 0);
                             logBootCompletedBroadcastCompletionLatencyIfPossible(r);
                             // Set this to null so that the reference
                             // (local and remote) isn't kept in the mBroadcastHistory.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index cbc7540..8380308 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -719,11 +719,12 @@
     private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) {
         for (int i = 0; i < replacedBroadcasts.size(); ++i) {
             final BroadcastRecord r = replacedBroadcasts.valueAt(i);
-            r.resultCode = Activity.RESULT_CANCELED;
-            r.resultData = null;
-            r.resultExtras = null;
-            scheduleResultTo(r);
-            notifyFinishBroadcast(r);
+            // Skip all the receivers in the replaced broadcast
+            for (int rcvrIdx = 0; rcvrIdx < r.receivers.size(); ++rcvrIdx) {
+                if (!isDeliveryStateTerminal(r.getDeliveryState(rcvrIdx))) {
+                    mBroadcastConsumerSkipAndCanceled.accept(r, rcvrIdx);
+                }
+            }
         }
     }
 
@@ -1932,7 +1933,8 @@
             FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
                     receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
                     app != null ? app.info.packageName : null, r.callerPackage,
-                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
+                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
+                    BroadcastRecord.getReceiverPriority(receiver), r.callerProcState);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index cfdb133..67d43fd 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,6 +44,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
+import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.BackgroundStartPrivileges;
@@ -93,6 +95,7 @@
     final @Nullable String callerFeatureId; // which feature in the package sent this
     final int callingPid;   // the pid of who sent this
     final int callingUid;   // the uid of who sent this
+    final @ProcessState int callerProcState; // Procstate of the caller process at enqueue time.
 
     final int originalStickyCallingUid;
             // if this is a sticky broadcast, the Uid of the original sender
@@ -462,6 +465,8 @@
         callerFeatureId = _callerFeatureId;
         callingPid = _callingPid;
         callingUid = _callingUid;
+        callerProcState = callerApp == null ? ActivityManager.PROCESS_STATE_UNKNOWN
+                : callerApp.getCurProcState();
         callerInstantApp = _callerInstantApp;
         callerInstrumented = isCallerInstrumented(_callerApp, _callingUid);
         resolvedType = _resolvedType;
@@ -515,6 +520,7 @@
         callerFeatureId = from.callerFeatureId;
         callingPid = from.callingPid;
         callingUid = from.callingUid;
+        callerProcState = from.callerProcState;
         callerInstantApp = from.callerInstantApp;
         callerInstrumented = from.callerInstrumented;
         ordered = from.ordered;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index d6495c7..267d246 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -669,6 +669,11 @@
         return mOnewayThread;
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurProcState() {
+        return mState.getCurProcState();
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
         mProfile.onProcessActive(thread, tracker);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d0b6cdc..336f0fb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3277,6 +3277,7 @@
             if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
                 streamType = mVolumeControlStream;
             } else {
+                // TODO discard activity on a muted stream?
                 final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
                 final boolean activeForReal;
                 if (maybeActiveStreamType == AudioSystem.STREAM_RING
@@ -3480,9 +3481,10 @@
             }
         } else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) {
             // if the stream is currently muted streams by ringer/zen mode
-            // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES)
+            // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES) with an unmute or raise
             if (direction == AudioManager.ADJUST_TOGGLE_MUTE
-                    || direction == AudioManager.ADJUST_UNMUTE) {
+                    || direction == AudioManager.ADJUST_UNMUTE
+                    || direction == AudioManager.ADJUST_RAISE) {
                 adjustVolume = false;
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index aa5f9fa..7fa4d6c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -573,7 +573,7 @@
         final BiometricSchedulerOperation operation = mCurrentOperation;
         mHandler.postDelayed(() -> {
             if (operation == mCurrentOperation) {
-                Counter.logIncrement("biometric.scheduler_watchdog_triggered_count");
+                Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count");
                 clearScheduler();
             }
         }, 10000);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aeff2b0..b39e860 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -119,6 +119,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -223,6 +224,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -234,6 +237,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.VibrationEffect;
+import android.os.WorkSource;
 import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -559,6 +563,7 @@
     private PermissionHelper mPermissionHelper;
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     private TelecomManager mTelecomManager;
+    private PowerManager mPowerManager;
     private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
 
     final IBinder mForegroundToken = new Binder();
@@ -923,7 +928,7 @@
         if (oldFlags != flags) {
             summary.getSbn().getNotification().flags = flags;
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
-                    mPostNotificationTrackerFactory.newTracker()));
+                    mPostNotificationTrackerFactory.newTracker(null)));
         }
     }
 
@@ -1457,7 +1462,7 @@
                         // want to adjust the flag behaviour.
                         mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
                                 r, true /* isAppForeground*/,
-                                mPostNotificationTrackerFactory.newTracker()));
+                                mPostNotificationTrackerFactory.newTracker(null)));
                     }
                 }
             }
@@ -1488,7 +1493,7 @@
                         mHandler.post(
                                 new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
                                         /* foreground= */ true,
-                                        mPostNotificationTrackerFactory.newTracker()));
+                                        mPostNotificationTrackerFactory.newTracker(null)));
                     }
                 }
             }
@@ -2233,7 +2238,7 @@
             UsageStatsManagerInternal usageStatsManagerInternal,
             TelecomManager telecomManager, NotificationChannelLogger channelLogger,
             SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
-            PermissionManager permissionManager,
+            PermissionManager permissionManager, PowerManager powerManager,
             PostNotificationTrackerFactory postNotificationTrackerFactory) {
         mHandler = handler;
         Resources resources = getContext().getResources();
@@ -2265,6 +2270,7 @@
         mDpm = dpm;
         mUm = userManager;
         mTelecomManager = telecomManager;
+        mPowerManager = powerManager;
         mPostNotificationTrackerFactory = postNotificationTrackerFactory;
         mPlatformCompat = IPlatformCompat.Stub.asInterface(
                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2568,6 +2574,7 @@
                 getContext().getSystemService(TelecomManager.class),
                 new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
                 getContext().getSystemService(PermissionManager.class),
+                getContext().getSystemService(PowerManager.class),
                 new PostNotificationTrackerFactory() {});
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -2684,7 +2691,7 @@
                     final boolean isAppForeground =
                             mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                     mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
-                            mPostNotificationTrackerFactory.newTracker()));
+                            mPostNotificationTrackerFactory.newTracker(null)));
                 }
             }
 
@@ -6577,7 +6584,7 @@
     void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
             final int callingPid, final String tag, final int id, final Notification notification,
             int incomingUserId, boolean postSilently) {
-        PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker();
+        PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
         boolean enqueued = false;
         try {
             enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
@@ -6589,6 +6596,22 @@
         }
     }
 
+    private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
+        if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) {
+            // The package probably doesn't have WAKE_LOCK permission and should not require it.
+            return Binder.withCleanCallingIdentity(() -> {
+                WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                        "NotificationManagerService:post:" + pkg);
+                wakeLock.setWorkSource(new WorkSource(uid, pkg));
+                // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
+                wakeLock.acquire(30_000);
+                return mPostNotificationTrackerFactory.newTracker(wakeLock);
+            });
+        } else {
+            return mPostNotificationTrackerFactory.newTracker(null);
+        }
+    }
+
     /**
      * @return True if we successfully processed the notification and handed off the task of
      * enqueueing it to a background thread; false otherwise.
@@ -7106,7 +7129,7 @@
                             mHandler.post(
                                     new NotificationManagerService.EnqueueNotificationRunnable(
                                             r.getUser().getIdentifier(), r, isAppForeground,
-                                            mPostNotificationTrackerFactory.newTracker()));
+                                            mPostNotificationTrackerFactory.newTracker(null)));
                         }
                     }
                 }
@@ -12168,20 +12191,20 @@
     }
 
     interface PostNotificationTrackerFactory {
-        default PostNotificationTracker newTracker() {
-            return new PostNotificationTracker();
+        default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) {
+            return new PostNotificationTracker(optionalWakelock);
         }
     }
 
     static class PostNotificationTracker {
         @ElapsedRealtimeLong private final long mStartTime;
-        @Nullable private NotificationRecordLogger.NotificationReported mReport;
+        @Nullable private final WakeLock mWakeLock;
         private boolean mOngoing;
 
         @VisibleForTesting
-        PostNotificationTracker() {
-            // TODO(b/275044361): (Conditionally) receive a wakelock.
+        PostNotificationTracker(@Nullable WakeLock wakeLock) {
             mStartTime = SystemClock.elapsedRealtime();
+            mWakeLock = wakeLock;
             mOngoing = true;
             if (DBG) {
                 Slog.d(TAG, "PostNotification: Started");
@@ -12199,9 +12222,8 @@
         }
 
         /**
-         * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either
-         * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before
-         * it's discarded.
+         * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or
+         * {@link #cancel} (exclusively) should be called on this object before it's discarded.
          */
         void cancel() {
             if (!isOngoing()) {
@@ -12209,9 +12231,9 @@
                 return;
             }
             mOngoing = false;
-
-            // TODO(b/275044361): Release wakelock.
-
+            if (mWakeLock != null) {
+                Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+            }
             if (DBG) {
                 long elapsedTime = SystemClock.elapsedRealtime() - mStartTime;
                 Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms",
@@ -12220,9 +12242,9 @@
         }
 
         /**
-         * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the
-         * time elapsed since the operation started, in milliseconds. Either {@link #finish} or
-         * {@link #cancel} (exclusively) should be called on this object before it's discarded.
+         * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since
+         * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel}
+         * (exclusively) should be called on this object before it's discarded.
          */
         @DurationMillisLong
         long finish() {
@@ -12232,9 +12254,9 @@
                 return elapsedTime;
             }
             mOngoing = false;
-
-            // TODO(b/275044361): Release wakelock.
-
+            if (mWakeLock != null) {
+                Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+            }
             if (DBG) {
                 Slog.d(TAG,
                         TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 06db5be..50f1673 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2145,7 +2145,7 @@
         final String pkgName = pkg.getPackageName();
         final int[] installedForUsers = installRequest.getOriginUsers();
         final int installReason = installRequest.getInstallReason();
-        final String installerPackageName = installRequest.getSourceInstallerPackageName();
+        final String installerPackageName = installRequest.getInstallerPackageName();
 
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
         synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 95e7904..3464874 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -366,12 +366,6 @@
     public String getApexModuleName() {
         return mApexModuleName;
     }
-
-    @Nullable
-    public String getSourceInstallerPackageName() {
-        return mInstallArgs.mInstallSource.mInstallerPackageName;
-    }
-
     public boolean isRollback() {
         return mInstallArgs != null
                 && mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6491fd1..a911537 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -511,7 +511,7 @@
             }
         } catch (FileNotFoundException e) {
             // Missing sessions are okay, probably first boot
-        } catch (IOException | XmlPullParserException e) {
+        } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
             Slog.wtf(TAG, "Failed reading install sessions", e);
         } finally {
             IoUtils.closeQuietly(fis);
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 19aa4f8..54ca426 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -230,7 +230,9 @@
                             + Log.getStackTraceString(e));
         }
 
-        mCurrentFile.delete();
+        if (!mCurrentFile.delete()) {
+            throw new IllegalStateException("Failed to remove " + mCurrentFile);
+        }
         mCurrentFile = null;
     }
 
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d108e14..d55f85c 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -986,11 +986,14 @@
     }
 
     /** @see ApplicationInfo#privateFlagsExt */
-    public static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
+    private static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
                                              @Nullable PackageStateInternal pkgSetting) {
         // @formatter:off
-        // TODO: Add state specific flags
-        return pkgWithoutStateFlags;
+        int flags = pkgWithoutStateFlags;
+        if (pkgSetting != null) {
+            flags |= flag(pkgSetting.getCpuAbiOverride() != null, ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE);
+        }
+        return flags;
         // @formatter:on
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b2100e..9f16a844 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4281,6 +4281,7 @@
 
         mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
         mTaskSupervisor.mStoppingActivities.remove(this);
+        mLetterboxUiController.destroy();
         waitingToShow = false;
 
         // Defer removal of this activity when either a child is animating, or app transition is on
@@ -4350,8 +4351,6 @@
             mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
         }
 
-        mLetterboxUiController.destroy();
-
         if (!delayed) {
             updateReportedVisibilityLocked();
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0ebec11..a0a45e6 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1573,8 +1573,10 @@
             // existence change.
             transitionController.collectExistenceChange(started);
         } else if (result == START_DELIVERED_TO_TOP && newTransition != null
-                // An activity has changed order/visibility so this isn't just deliver-to-top
-                && mMovedToTopActivity == null) {
+                // An activity has changed order/visibility or the task is occluded by a transient
+                // activity, so this isn't just deliver-to-top
+                && mMovedToTopActivity == null
+                && !transitionController.isTransientHide(startedActivityRootTask)) {
             // We just delivered to top, so there isn't an actual transition here.
             if (!forceTransientTransition) {
                 newTransition.abort();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a650d77..d332dfd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2012,7 +2012,7 @@
             synchronized (mGlobalLock) {
                 final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
                 if (dc == null) return;
-                final Task task = dc.getTask((t) -> t.isLeafTask() && t.isFocusable(),
+                final Task task = dc.getTask((t) -> t.isLeafTask() && t.isTopActivityFocusable(),
                         true /*  traverseTopToBottom */);
                 if (task == null) return;
                 setFocusedTask(task.mTaskId, null /* touchedActivity */);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 123a74d..f7ccc0d 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Handler;
@@ -178,6 +179,14 @@
      * @param r activity record for which the warning may be displayed
      */
     public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
+        final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
+                & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
+        if (isUsingAbiOverride) {
+            // The abiOverride flag was specified during installation, which means that if the app
+            // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
+            return;
+        }
+        // The warning dialog can also be disabled for debugging purpose
         final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
                 "debug.wm.disable_deprecated_abi_dialog", false);
         if (disableDeprecatedAbiDialog) {
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 31b1069..db4762e 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -111,9 +111,13 @@
     }
 
     /**
-     * @return true if the rotation direction on the Z axis should be reversed.
+     * @return true if the rotation direction on the Z axis should be reversed for the default
+     * display.
      */
-    boolean shouldReverseRotationDirectionAroundZAxis() {
+    boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) {
+        if (!displayContent.isDefaultDisplay) {
+            return false;
+        }
         return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8be36f0..b681c19 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -950,7 +950,7 @@
     }
 
     void freezeRotation(int rotation) {
-        if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+        if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
             rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
         }
 
@@ -1225,7 +1225,7 @@
         if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
             sensorRotation = -1;
         }
-        if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+        if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
             sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
         }
         mLastSensorRotation = sensorRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 1fbf593..2b34bb2 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -42,6 +42,7 @@
 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.hardware.camera2.CameraManager;
 import android.os.Handler;
@@ -423,7 +424,18 @@
             // for the activity embedding case.
             if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
                     && isTreatmentEnabledForActivity(topActivity, /* mustBeFullscreen */ false)) {
-                showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
+                final PackageManager packageManager = mWmService.mContext.getPackageManager();
+                try {
+                    showToast(
+                            R.string.display_rotation_camera_compat_toast_in_multi_window,
+                            (String) packageManager.getApplicationLabel(
+                                    packageManager.getApplicationInfo(packageName, /* flags */ 0)));
+                } catch (PackageManager.NameNotFoundException e) {
+                    ProtoLog.e(WM_DEBUG_ORIENTATION,
+                            "DisplayRotationCompatPolicy: Multi-window toast not shown as "
+                                    + "package '%s' cannot be found.",
+                            packageName);
+                }
             }
         }
     }
@@ -434,6 +446,15 @@
                 () -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
     }
 
+    @VisibleForTesting
+    void showToast(@StringRes int stringRes, @NonNull String applicationLabel) {
+        UiThread.getHandler().post(
+                () -> Toast.makeText(
+                        mWmService.mContext,
+                        mWmService.mContext.getString(stringRes, applicationLabel),
+                        Toast.LENGTH_LONG).show());
+    }
+
     private synchronized void notifyCameraClosed(@NonNull String cameraId) {
         ProtoLog.v(WM_DEBUG_ORIENTATION,
                 "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c6c3b14..0c1f33c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1017,8 +1017,11 @@
         if (isTopActivityLaunchedBehind()) {
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
+        final WindowContainer<?> parent = getParent();
         final Task thisTask = asTask();
-        if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
+        if (thisTask != null && parent.asTask() == null
+                && mTransitionController.isTransientHide(thisTask)) {
+            // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule.
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
 
@@ -1028,7 +1031,6 @@
 
         // This TaskFragment is only considered visible if all its parent TaskFragments are
         // considered visible, so check the visibility of all ancestor TaskFragment first.
-        final WindowContainer parent = getParent();
         if (parent.asTaskFragment() != null) {
             final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
             if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 774f62d..fd478dc 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -31,6 +31,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -245,10 +246,13 @@
             intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
 
+            ActivityOptions activityOptions = ActivityOptions.makeBasic()
+                    .setPendingIntentCreatorBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
             IntentSender intentSender = PendingIntent.getActivityAsUser(
                     mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
-                    | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
-                    null, new UserHandle(mUserId)) .getIntentSender();
+                            | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+                    activityOptions.toBundle(), new UserHandle(mUserId)).getIntentSender();
 
             Bundle result = new Bundle();
             result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376..ca4a404 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -66,6 +66,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
@@ -2506,6 +2507,7 @@
     }
 
     @LargeTest
+    @FlakyTest(bugId = 283797480)
     public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
         // checkSignatures should be successful when both apps have been signed with the same
         // rotated key since the initial signature comparison between the two apps should
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 948687a..582685c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1407,7 +1407,7 @@
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
                 anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
-                anyString(), anyInt(), anyInt()),
+                anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
                 times(1));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 4989f84..03231ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1904,6 +1904,34 @@
     }
 
     @Test
+    public void testReplacePending_diffReceivers() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+        final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+        final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+        final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+        final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+                .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(receiverGreen, 10),
+                withPriority(receiverBlue, 5),
+                withPriority(receiverYellow, 0))));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(receiverGreen, 10),
+                withPriority(receiverBlue, 5))));
+
+        waitForIdle();
+
+        verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+        verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+    }
+
+    @Test
     public void testIdleAndBarrier() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index f44c1d1..4315254 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
@@ -36,6 +37,7 @@
     <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
     <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9166b3d..2a0c745 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -61,6 +61,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -80,6 +81,7 @@
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
@@ -119,12 +121,14 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -181,11 +185,14 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.WorkSource;
 import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
@@ -351,6 +358,9 @@
     private PermissionManager mPermissionManager;
     @Mock
     private DevicePolicyManagerInternal mDevicePolicyManager;
+    @Mock
+    private PowerManager mPowerManager;
+    private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>();
     private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
             new TestPostNotificationTrackerFactory();
 
@@ -431,8 +441,9 @@
         private final List<PostNotificationTracker> mCreatedTrackers = new ArrayList<>();
 
         @Override
-        public PostNotificationTracker newTracker() {
-            PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker();
+        public PostNotificationTracker newTracker(@Nullable WakeLock optionalWakeLock) {
+            PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker(
+                    optionalWakeLock);
             mCreatedTrackers.add(tracker);
             return tracker;
         }
@@ -563,6 +574,20 @@
 
         when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
 
+        // Use the real PowerManager to back up the mock w.r.t. creating WakeLocks.
+        // This is because 1) we need a mock to verify() calls and tracking the created WakeLocks,
+        // but 2) PowerManager and WakeLock perform their own checks (e.g. correct arguments, don't
+        // call release twice, etc) and we want the test to fail if such misuse happens, too.
+        PowerManager realPowerManager = mContext.getSystemService(PowerManager.class);
+        when(mPowerManager.newWakeLock(anyInt(), anyString())).then(
+                (Answer<WakeLock>) invocation -> {
+                    WakeLock wl = realPowerManager.newWakeLock(invocation.getArgument(0),
+                            invocation.getArgument(1));
+                    mAcquiredWakeLocks.add(wl);
+                    return wl;
+                });
+        mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+
         // apps allowed as convos
         mService.setStringArrayResourceValue(PKG_O);
 
@@ -579,7 +604,7 @@
                 mock(TelephonyManager.class),
                 mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
                 mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
-                mPostNotificationTrackerFactory);
+                mPowerManager, mPostNotificationTrackerFactory);
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -686,6 +711,13 @@
     }
 
     @After
+    public void assertAllWakeLocksReleased() {
+        for (WakeLock wakeLock : mAcquiredWakeLocks) {
+            assertThat(wakeLock.isHeld()).isFalse();
+        }
+    }
+
+    @After
     public void tearDown() throws Exception {
         if (mFile != null) mFile.delete();
         clearDeviceConfig();
@@ -1486,7 +1518,7 @@
 
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -1507,7 +1539,7 @@
 
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -1803,6 +1835,112 @@
     }
 
     @Test
+    public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "enqueueNotification_acquiresAndReleasesWakeLock", 0,
+                generateNotificationRecord(null).getNotification(), 0);
+
+        verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+        waitForIdle();
+
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+    }
+
+    @Test
+    public void enqueueNotification_throws_acquiresAndReleasesWakeLock() throws Exception {
+        // Simulate not enqueued due to rejected inputs.
+        assertThrows(Exception.class,
+                () -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                        "enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
+                        /* notification= */ null, 0));
+
+        verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+    }
+
+    @Test
+    public void enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock() throws Exception {
+        // Simulate not enqueued due to snoozing inputs.
+        when(mSnoozeHelper.getSnoozeContextForUnpostedNotification(anyInt(), any(), any()))
+                .thenReturn("zzzzzzz");
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
+                generateNotificationRecord(null).getNotification(), 0);
+
+        verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+        waitForIdle();
+
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+    }
+
+    @Test
+    public void enqueueNotification_notPosted_acquiresAndReleasesWakeLock() throws Exception {
+        // Simulate enqueued but not posted due to missing small icon.
+        Notification notif = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
+                notif, 0);
+
+        verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+        waitForIdle();
+
+        // NLSes were not called.
+        verify(mListeners, never()).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+        assertThat(mAcquiredWakeLocks).hasSize(1);
+        assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+    }
+
+    @Test
+    public void enqueueNotification_setsWakeLockWorkSource() throws Exception {
+        // Use a "full" mock for the PowerManager (instead of the one that delegates to the real
+        // service) so we can return a mocked WakeLock that we can verify() on.
+        reset(mPowerManager);
+        WakeLock wakeLock = mock(WakeLock.class);
+        when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(wakeLock);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "enqueueNotification_setsWakeLockWorkSource", 0,
+                generateNotificationRecord(null).getNotification(), 0);
+        waitForIdle();
+
+        InOrder inOrder = inOrder(mPowerManager, wakeLock);
+        inOrder.verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+        inOrder.verify(wakeLock).setWorkSource(eq(new WorkSource(mUid, PKG)));
+        inOrder.verify(wakeLock).acquire(anyLong());
+        inOrder.verify(wakeLock).release();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception {
+        mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "enqueueNotification_setsWakeLockWorkSource", 0,
+                generateNotificationRecord(null).getNotification(), 0);
+        waitForIdle();
+
+        verifyZeroInteractions(mPowerManager);
+    }
+
+    @Test
     public void testCancelNonexistentNotification() throws Exception {
         mBinderService.cancelNotificationWithTag(PKG, PKG,
                 "testCancelNonexistentNotification", 0, 0);
@@ -4361,7 +4499,7 @@
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -4380,7 +4518,7 @@
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(update.getKey(),
                         update.getSbn().getPackageName(), update.getUid(),
-                        mPostNotificationTrackerFactory.newTracker());
+                        mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -4400,7 +4538,7 @@
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(update.getKey(),
                         update.getSbn().getPackageName(), update.getUid(),
-                        mPostNotificationTrackerFactory.newTracker());
+                        mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -4420,7 +4558,7 @@
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(update.getKey(),
                         update.getSbn().getPackageName(),
-                        update.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        update.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -4434,13 +4572,13 @@
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
 
         r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
         r.setCriticality(CriticalNotificationExtractor.CRITICAL);
         runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         mService.addEnqueuedNotification(r);
 
         runnable.run();
@@ -5090,7 +5228,7 @@
                 mService.new PostNotificationRunnable(original.getKey(),
                         original.getSbn().getPackageName(),
                         original.getUid(),
-                        mPostNotificationTrackerFactory.newTracker());
+                        mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -5114,7 +5252,7 @@
                 mService.new PostNotificationRunnable(update.getKey(),
                         update.getSbn().getPackageName(),
                         update.getUid(),
-                        mPostNotificationTrackerFactory.newTracker());
+                        mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -7536,7 +7674,7 @@
 
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -10234,7 +10372,7 @@
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                        r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -10251,7 +10389,7 @@
 
         mService.addEnqueuedNotification(r);
         runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -10268,7 +10406,7 @@
 
         mService.addEnqueuedNotification(r);
         runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                r.getUid(), mPostNotificationTrackerFactory.newTracker());
+                r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
         runnable.run();
         waitForIdle();
 
@@ -10361,7 +10499,7 @@
         // normal blocked notifications - blocked
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats).registerBlocked(any());
@@ -10379,7 +10517,7 @@
 
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats).registerBlocked(any());
@@ -10392,7 +10530,7 @@
 
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats, never()).registerBlocked(any());
@@ -10406,7 +10544,7 @@
 
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats, never()).registerBlocked(any());
@@ -10420,7 +10558,7 @@
 
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats).registerBlocked(any());
@@ -10435,7 +10573,7 @@
 
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats).registerBlocked(any());
@@ -10448,7 +10586,7 @@
 
         mService.addEnqueuedNotification(r);
         mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
-                mPostNotificationTrackerFactory.newTracker()).run();
+                mPostNotificationTrackerFactory.newTracker(null)).run();
         waitForIdle();
 
         verify(mUsageStats).registerBlocked(any());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 66c1e35..81c573d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,6 +48,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.PermissionManager;
@@ -169,6 +170,7 @@
                     mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
                     mock(NotificationChannelLogger.class), new TestableFlagResolver(),
                     mock(PermissionManager.class),
+                    mock(PowerManager.class),
                     new NotificationManagerService.PostNotificationTrackerFactory() {});
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 0044e2e..4290f4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -1059,4 +1060,18 @@
         assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
         mAtm.mInternal.unregisterActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID);
     }
+
+    @Test
+    public void testFocusTopTask() {
+        final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
+                .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
+                .build();
+        final Task pinnedTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .build();
+        mAtm.focusTopTask(mDisplayContent.mDisplayId);
+
+        assertTrue(homeActivity.getTask().isFocused());
+        assertFalse(pinnedTask.isFocused());
+    }
 }
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 3ca35ef..8e015d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -40,7 +40,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -51,6 +53,8 @@
 import android.app.servertransaction.ResumeActivityItem;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Configuration.Orientation;
 import android.hardware.camera2.CameraManager;
@@ -84,7 +88,7 @@
     private static final String TEST_PACKAGE_2 = "com.test.package.two";
     private static final String CAMERA_ID_1 = "camera-1";
     private static final String CAMERA_ID_2 = "camera-2";
-
+    private static final String TEST_PACKAGE_1_LABEL = "testPackage1";
     private CameraManager mMockCameraManager;
     private Handler mMockHandler;
     private LetterboxConfiguration mLetterboxConfiguration;
@@ -133,17 +137,27 @@
     }
 
     @Test
-    public void testOpenedCameraInSplitScreen_showToast() {
+    public void testOpenedCameraInSplitScreen_showToast() throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         spyOn(mTask);
         spyOn(mDisplayRotationCompatPolicy);
         doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
         doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
 
+        final PackageManager mockPackageManager = mock(PackageManager.class);
+        final ApplicationInfo mockApplicationInfo = mock(ApplicationInfo.class);
+        when(mContext.getPackageManager()).thenReturn(mockPackageManager);
+        when(mockPackageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenReturn(mockApplicationInfo);
+
+        doReturn(TEST_PACKAGE_1_LABEL).when(mockPackageManager)
+                .getApplicationLabel(mockApplicationInfo);
+
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
         verify(mDisplayRotationCompatPolicy).showToast(
-                R.string.display_rotation_camera_compat_toast_in_split_screen);
+                R.string.display_rotation_camera_compat_toast_in_multi_window,
+                TEST_PACKAGE_1_LABEL);
     }
 
     @Test
@@ -157,7 +171,8 @@
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
         verify(mDisplayRotationCompatPolicy, never()).showToast(
-                R.string.display_rotation_camera_compat_toast_in_split_screen);
+                R.string.display_rotation_camera_compat_toast_in_multi_window,
+                TEST_PACKAGE_1_LABEL);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 04e1d9c..2a8f0ffc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -59,10 +59,10 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
 import android.os.SystemClock;
-import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.view.DisplayAddress;
@@ -518,7 +518,8 @@
         mBuilder.build();
         configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
 
-        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+                .thenReturn(true);
 
         thawRotation();
 
@@ -544,7 +545,8 @@
     @Test
     public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception {
         mBuilder.build();
-        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+                .thenReturn(true);
 
         freezeRotation(Surface.ROTATION_90);
         assertEquals(Surface.ROTATION_270, mTarget.getUserRotation());
@@ -553,7 +555,8 @@
     @Test
     public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception {
         mBuilder.build();
-        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false);
+        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+                .thenReturn(false);
 
         freezeRotation(Surface.ROTATION_90);
         assertEquals(Surface.ROTATION_90, mTarget.getUserRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index fb29d3a..abf21a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -641,6 +641,31 @@
         // Home split secondary and home task should be invisible.
         assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 splitSecondary.getVisibility(null /* starting */));
+
+        // Put another task on top of primary split.
+        final Task topSplitPrimary = new TaskBuilder(mSupervisor).setParentTask(organizer.mPrimary)
+                .setCreateActivity(true).build();
+        doReturn(false).when(topSplitPrimary).isTranslucent(any());
+        // Convert the fullscreen translucent task to opaque.
+        doReturn(false).when(translucentRootTask).isTranslucent(any());
+        translucentRootTask.moveToFront("test");
+        // The tasks of primary split are occluded by the fullscreen opaque task.
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                organizer.mPrimary.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                topSplitPrimary.getVisibility(null /* starting */));
+        // Make primary split root transient-hide.
+        spyOn(splitPrimary.mTransitionController);
+        doReturn(true).when(splitPrimary.mTransitionController).isTransientHide(
+                organizer.mPrimary);
+        // The split root and its top become visible.
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                organizer.mPrimary.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                topSplitPrimary.getVisibility(null /* starting */));
+        // The bottom of primary split becomes invisible because it is occluded by topSplitPrimary.
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                splitPrimary.getVisibility(null /* starting */));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e74ad33..082122e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -406,7 +406,6 @@
         clearInvocations(translucentActivity.mLetterboxUiController);
 
         // We destroy the first opaque activity
-        mActivity.setState(DESTROYED, "testing");
         mActivity.removeImmediately();
 
         // Check that updateInheritedLetterbox() is invoked again
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index bedf099..bfebb42 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -19,6 +19,7 @@
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
     </target_preparer>