Merge "Non-functionally simplify getRoutingSession() and fix nullability" into main
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index c9c91fc..efbd96b 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -591,9 +591,14 @@
     /**
      * Scale given vibration intensity by the given factor.
      *
+     * <p> This scale is not necessarily linear and may apply a gamma correction to the scale
+     * factor before using it.
+     *
      * @param intensity   relative intensity of the effect, must be between 0 and 1
      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
      *                    scale down the intensity, values larger than 1 will scale up
+     * @return the scaled intensity which will be values within [0, 1].
+     *
      * @hide
      */
     public static float scale(float intensity, float scaleFactor) {
@@ -624,6 +629,20 @@
     }
 
     /**
+     * Performs a linear scaling on the given vibration intensity by the given factor.
+     *
+     * @param intensity relative intensity of the effect, must be between 0 and 1.
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up.
+     * @return the scaled intensity which will be values within [0, 1].
+     *
+     * @hide
+     */
+    public static float scaleLinearly(float intensity, float scaleFactor) {
+        return MathUtils.constrain(intensity * scaleFactor, 0f, 1f);
+    }
+
+    /**
      * Returns a compact version of the {@link #toString()} result for debugging purposes.
      *
      * @hide
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index a035092..39f8412 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -137,6 +137,14 @@
     /** @hide */
     @NonNull
     @Override
+    public PrebakedSegment scaleLinearly(float scaleFactor) {
+        // Prebaked effect strength cannot be scaled with this method.
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
     public PrebakedSegment applyEffectStrength(int effectStrength) {
         if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
             return new PrebakedSegment(mEffectId, mFallback, effectStrength);
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 95d97bf..3c84bcd 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -98,8 +98,24 @@
     @NonNull
     @Override
     public PrimitiveSegment scale(float scaleFactor) {
-        return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
-                mDelay);
+        float newScale = VibrationEffect.scale(mScale, scaleFactor);
+        if (Float.compare(mScale, newScale) == 0) {
+            return this;
+        }
+
+        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
+    public PrimitiveSegment scaleLinearly(float scaleFactor) {
+        float newScale = VibrationEffect.scaleLinearly(mScale, scaleFactor);
+        if (Float.compare(mScale, newScale) == 0) {
+            return this;
+        }
+
+        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
     }
 
     /** @hide */
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 5f9d102..09d2e26 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -159,6 +159,21 @@
     /** @hide */
     @NonNull
     @Override
+    public RampSegment scaleLinearly(float scaleFactor) {
+        float newStartAmplitude = VibrationEffect.scaleLinearly(mStartAmplitude, scaleFactor);
+        float newEndAmplitude = VibrationEffect.scaleLinearly(mEndAmplitude, scaleFactor);
+        if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
+                && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
+            return this;
+        }
+        return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz,
+                mEndFrequencyHz,
+                mDuration);
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
     public RampSegment applyEffectStrength(int effectStrength) {
         return this;
     }
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 9576a5b..fa083c1 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -137,8 +137,25 @@
         if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
             return this;
         }
-        return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequencyHz,
-                mDuration);
+        float newAmplitude = VibrationEffect.scale(mAmplitude, scaleFactor);
+        if (Float.compare(newAmplitude, mAmplitude) == 0) {
+            return this;
+        }
+        return new StepSegment(newAmplitude, mFrequencyHz, mDuration);
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
+    public StepSegment scaleLinearly(float scaleFactor) {
+        if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+            return this;
+        }
+        float newAmplitude = VibrationEffect.scaleLinearly(mAmplitude, scaleFactor);
+        if (Float.compare(newAmplitude, mAmplitude) == 0) {
+            return this;
+        }
+        return new StepSegment(newAmplitude, mFrequencyHz, mDuration);
     }
 
     /** @hide */
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 17ac36f..e1fb4e3 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -96,6 +96,9 @@
     /**
      * Scale the segment intensity with the given factor.
      *
+     * <p> This scale is not necessarily linear and may apply a gamma correction to the scale
+     * factor before using it.
+     *
      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
      *                    scale down the intensity, values larger than 1 will scale up
      *
@@ -105,6 +108,17 @@
     public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
 
     /**
+     * Performs a linear scaling on the segment intensity with the given factor.
+     *
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up
+     *
+     * @hide
+     */
+    @NonNull
+    public abstract <T extends VibrationEffectSegment> T scaleLinearly(float scaleFactor);
+
+    /**
      * Applies given effect strength to prebaked effects.
      *
      * @param effectStrength new effect strength to be applied, one of
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 4f5f3c0..7dd9e55 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -106,6 +106,13 @@
     }
 
     @Test
+    public void testScaleLinearly_ignoresAndReturnsSameEffect() {
+        PrebakedSegment prebaked = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertSame(prebaked, prebaked.scaleLinearly(0.5f));
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(-1, new PrebakedSegment(
                 VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index ec5a084..e9a08ae 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -129,6 +129,27 @@
     }
 
     @Test
+    public void testScaleLinearly() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+        assertEquals(1f, initial.scaleLinearly(1).getScale(), TOLERANCE);
+        assertEquals(0.5f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE);
+        assertEquals(1f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE);
+        assertEquals(0.8f, initial.scaleLinearly(0.8f).getScale(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE);
+
+        initial = new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
+
+        assertEquals(0f, initial.scaleLinearly(1).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE);
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(-1, new PrimitiveSegment(
                 VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).getDuration());
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 5caa86b..01013ab 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -131,6 +131,37 @@
     }
 
     @Test
+    public void testScaleLinearly() {
+        RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+        assertEquals(0f, initial.scaleLinearly(1f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getStartAmplitude(),
+                TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(),
+                TOLERANCE);
+
+        assertEquals(1f, initial.scaleLinearly(1f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scaleLinearly(0.5f).getEndAmplitude(), TOLERANCE);
+        assertEquals(1f, initial.scaleLinearly(1.5f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.8f, initial.scaleLinearly(0.8f).getEndAmplitude(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(0.8f, initial.scaleLinearly(1.5f).scaleLinearly(0.8f).getEndAmplitude(),
+                TOLERANCE);
+
+        initial = new RampSegment(0.5f, 1, 0, 0, 0);
+
+        assertEquals(0.5f, initial.scaleLinearly(1).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.25f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.75f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.4f, initial.scaleLinearly(0.8f).getStartAmplitude(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(0.5f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(),
+                TOLERANCE);
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration());
     }
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 44db306..40776ab 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -141,6 +141,37 @@
     }
 
     @Test
+    public void testScaleLinearly_fullAmplitude() {
+        StepSegment initial = new StepSegment(1f, 0, 0);
+
+        assertEquals(1f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE);
+        assertEquals(1f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0.8f, initial.scaleLinearly(0.8f).getAmplitude(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getAmplitude(),
+                TOLERANCE);
+
+        initial = new StepSegment(0, 0, 0);
+
+        assertEquals(0f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScaleLinearly_defaultAmplitude() {
+        StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
+
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1).getAmplitude(),
+                TOLERANCE);
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(0.5f).getAmplitude(),
+                TOLERANCE);
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1.5f).getAmplitude(),
+                TOLERANCE);
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(5, new StepSegment(0, 0, 5).getDuration());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6de5d74..645ff06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -255,7 +255,10 @@
                 2 -> {
                     // Split-screen case where there are two focused tasks, then we find the child
                     // task to move to desktop.
-                    val splitFocusedTask = findChildFocusedTask(allFocusedTasks)
+                    val splitFocusedTask =
+                        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+                            allFocusedTasks[1]
+                        else allFocusedTasks[0]
                     moveToDesktop(splitFocusedTask)
                 }
                 1 -> {
@@ -263,21 +266,17 @@
                     moveToDesktop(allFocusedTasks[0].taskId)
                 }
                 else -> {
-                    KtProtoLog.v(
+                    KtProtoLog.w(
                         WM_SHELL_DESKTOP_MODE,
-                        "DesktopTasksController: Cannot enter desktop expected less " +
-                                "than 3 focused tasks but found " + allFocusedTasks.size
+                        "DesktopTasksController: Cannot enter desktop, expected less " +
+                                "than 3 focused tasks but found %d",
+                        allFocusedTasks.size
                     )
                 }
             }
         }
     }
 
-    private fun findChildFocusedTask(allFocusedTasks: List<RunningTaskInfo>): RunningTaskInfo {
-        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) return allFocusedTasks[1]
-        return allFocusedTasks[0]
-    }
-
     /** Move a task with given `taskId` to desktop */
     fun moveToDesktop(
             taskId: Int,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index acb1418..994a020 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -499,3 +499,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "clipboard_noninteractive_on_lockscreen"
+    namespace: "systemui"
+    description: "Prevents the interactive clipboard UI from appearing when device is locked"
+    bug: "317048495"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
index c418490..7a73c58 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -73,7 +73,7 @@
         BurnInParameters(
             clockControllerProvider = { clock },
             topInset = topInset,
-            statusViewTop = topmostTop,
+            minViewY = topmostTop,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 74fa465..b0f59fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -127,7 +127,7 @@
     @Test
     fun translationAndScale_whenFullyDozing() =
         testScope.runTest {
-            burnInParameters = burnInParameters.copy(statusViewTop = 100)
+            burnInParameters = burnInParameters.copy(minViewY = 100)
             val translationX by collectLastValue(underTest.translationX(burnInParameters))
             val translationY by collectLastValue(underTest.translationY(burnInParameters))
             val scale by collectLastValue(underTest.scale(burnInParameters))
@@ -182,11 +182,77 @@
         }
 
     @Test
-    fun translationAndScale_whenFullyDozing_staysOutOfTopInset() =
+    fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
             burnInParameters =
                 burnInParameters.copy(
-                    statusViewTop = 100,
+                    minViewY = 100,
+                    topInset = 80,
+                )
+            val translationX by collectLastValue(underTest.translationX(burnInParameters))
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            val scale by collectLastValue(underTest.scale(burnInParameters))
+
+            // Set to dozing (on AOD)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                ),
+                validateStep = false,
+            )
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = -30,
+                    scale = 0.5f,
+                )
+            assertThat(translationX).isEqualTo(20)
+            // -20 instead of -30, due to inset of 80
+            assertThat(translationY).isEqualTo(-20)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 0.5f,
+                        scaleClockOnly = true,
+                    )
+                )
+
+            // Set to the beginning of GONE->AOD transition
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED
+                ),
+                validateStep = false,
+            )
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 1f,
+                        scaleClockOnly = true,
+                    )
+                )
+        }
+
+    @Test
+    fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+            burnInParameters =
+                burnInParameters.copy(
+                    minViewY = 100,
                     topInset = 80,
                 )
             val translationX by collectLastValue(underTest.translationX(burnInParameters))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index c381749..31b67b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,6 +43,7 @@
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val repository = kosmos.fakeKeyguardTransitionRepository
+    val shadeRepository = kosmos.fakeShadeRepository
     val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     val underTest = kosmos.aodToLockscreenTransitionViewModel
 
@@ -59,6 +61,38 @@
         }
 
     @Test
+    fun notificationAlpha_whenShadeIsExpanded_equalsOne() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.notificationAlpha)
+
+            shadeRepository.setQsExpansion(0.5f)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(alpha).isEqualTo(1f)
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isEqualTo(1f)
+            repository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun notificationAlpha_whenShadeIsNotExpanded_usesTransitionValue() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.notificationAlpha)
+
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(alpha).isEqualTo(0f)
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isEqualTo(0.5f)
+            repository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
     fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
         testScope.runTest {
             val viewState = ViewStateAccessor(alpha = { 0.5f })
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt
new file mode 100644
index 0000000..62c9163
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MinimumTilesResourceRepositoryTest : SysuiTestCase() {
+
+    val testableResources = context.orCreateTestableResources
+
+    @Test
+    fun minimumQSTiles_followsConfig() {
+        val minTwo = 2
+        testableResources.addOverride(R.integer.quick_settings_min_num_tiles, minTwo)
+        val underTest = MinimumTilesResourceRepository(context.resources)
+        assertThat(underTest.minNumberOfTiles).isEqualTo(minTwo)
+
+        val minSix = 6
+        testableResources.addOverride(R.integer.quick_settings_min_num_tiles, minSix)
+        val otherUnderTest = MinimumTilesResourceRepository(context.resources)
+        assertThat(otherUnderTest.minNumberOfTiles).isEqualTo(minSix)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 3418977..37d4721 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -20,11 +20,11 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -187,6 +187,22 @@
             assertThat(loadTilesForUser(0)).isEqualTo(DEFAULT_TILES)
         }
 
+    @Test
+    fun prependDefault() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+            val startingTiles = listOf(TileSpec.create("e"), TileSpec.create("f"))
+
+            underTest.setTiles(0, startingTiles)
+            runCurrent()
+
+            underTest.prependDefault(0)
+
+            assertThat(tiles!!)
+                .containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles)
+        }
+
     private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
         secureSettings.putStringForUser(SETTING, specs, forUser)
         runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 1e2784a..634c5fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -40,6 +40,7 @@
 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.MinimumTilesFixedRepository
 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.QSPipelineFlagsRepository
@@ -82,6 +83,7 @@
         FakeCustomTileAddedRepository()
     private val pipelineFlags = QSPipelineFlagsRepository()
     private val tileLifecycleManagerFactory = TLMFactory()
+    private val minimumTilesRepository = MinimumTilesFixedRepository()
 
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
 
@@ -114,6 +116,7 @@
                 tileSpecRepository = tileSpecRepository,
                 installedTilesComponentRepository = installedTilesPackageRepository,
                 userRepository = userRepository,
+                minimumTilesRepository = minimumTilesRepository,
                 customTileStatePersister = customTileStatePersister,
                 tileFactory = tileFactory,
                 newQSTileFactory = { newQSTileFactory },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
new file mode 100644
index 0000000..90c8304
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This integration test is for testing the solution to b/324575996. In particular, when restoring
+ * from a device that uses different specs for tiles, we may end up with empty (or mostly empty) QS.
+ * In that case, we want to prepend the default tiles instead.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NoLowNumberOfTilesTest : SysuiTestCase() {
+
+    private val USER_0_INFO =
+        UserInfo(
+            0,
+            "zero",
+            "",
+            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+        )
+
+    private val defaultTiles =
+        listOf(
+            TileSpec.create("internet"),
+            TileSpec.create("bt"),
+        )
+
+    private val kosmos =
+        Kosmos().apply {
+            fakeMinimumTilesRepository = MinimumTilesFixedRepository(minNumberOfTiles = 2)
+            fakeUserTracker.set(listOf(USER_0_INFO), 0)
+            qsTileFactory = FakeQSFactory(::tileCreator)
+            fakeDefaultTilesRepository = FakeDefaultTilesRepository(defaultTiles)
+        }
+
+    private val currentUser: Int
+        get() = kosmos.userTracker.userId
+
+    private val goodTile = TileSpec.create("correct")
+
+    private val restoredTiles =
+        listOf(
+            TileSpec.create("OEM:internet"),
+            TileSpec.create("OEM:bt"),
+            TileSpec.create("OEM:dnd"),
+            // This is not an installed component so a tile won't be created
+            TileSpec.create(ComponentName.unflattenFromString("oem/.tile")!!),
+            TileSpec.create("OEM:flashlight"),
+            goodTile,
+        )
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+
+        with(kosmos) {
+            restoreReconciliationInteractor.start()
+            autoAddInteractor.init(kosmos.currentTilesInteractor)
+        }
+    }
+
+    @Test
+    fun noLessThanTwoTilesAfterOEMRestore_prependedDefault() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+                runCurrent()
+
+                assertThat(tiles!!).isNotEmpty()
+
+                val restoreData = RestoreData(restoredTiles, emptySet(), currentUser)
+                fakeRestoreRepository.onDataRestored(restoreData)
+                runCurrent()
+
+                assertThat(tiles!!.map { it.spec }).isEqualTo(defaultTiles + listOf(goodTile))
+            }
+        }
+
+    @Test
+    fun noEmptyTilesAfterSettingTilesToUnknownNames() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+                runCurrent()
+
+                assertThat(tiles!!).isNotEmpty()
+
+                val badTiles = listOf(TileSpec.create("OEM:unknown_tile"))
+                currentTilesInteractor.setTiles(badTiles)
+                runCurrent()
+
+                assertThat(tiles!!.map { it.spec }).isEqualTo(defaultTiles)
+            }
+        }
+
+    private fun tileCreator(spec: String): QSTile? {
+        return if (spec.contains("OEM")) {
+            null // We don't know how to create OEM spec tiles
+        } else {
+            FakeQSTile(currentUser)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 3e0082c..0641c61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -681,7 +681,7 @@
     @Test
     fun shadeCollapseFadeIn() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
+            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
             underTest.setShadeCollapseFadeInComplete(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index aa6b4ac..c804fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -1138,7 +1138,7 @@
 
     private fun setCameraProtectionBounds(protectionBounds: Rect) {
         val protectionInfo =
-            mock<CameraProtectionInfo> { whenever(this.cutoutBounds).thenReturn(protectionBounds) }
+            mock<CameraProtectionInfo> { whenever(this.bounds).thenReturn(protectionBounds) }
         whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index e8499d3..ca83724 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -143,7 +143,7 @@
 
     private fun notifyCameraActive(info: CameraProtectionInfo) {
         listeners.forEach {
-            it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+            it.onApplyCameraProtection(info.cutoutProtectionPath, info.bounds)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index 6314bd9..9357056 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -23,6 +23,6 @@
     val logicalCameraId: String,
     val physicalCameraId: String?,
     val cutoutProtectionPath: Path,
-    val cutoutBounds: Rect,
+    val bounds: Rect,
     val displayUniqueId: String?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
index aad9341..7309599 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -17,8 +17,12 @@
 package com.android.systemui
 
 import android.content.Context
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.Display
 import android.view.DisplayCutout
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.naturalBounds
 import javax.inject.Inject
 
 @SysUISingleton
@@ -33,15 +37,43 @@
         cameraProtectionLoader.loadCameraProtectionInfoList()
     }
 
-    fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+    /**
+     * Returns the [SysUICutoutInformation] for the current display and the current rotation.
+     *
+     * This means that the bounds of the display cutout and the camera protection will be
+     * adjusted/rotated for the current rotation.
+     */
+    fun cutoutInfoForCurrentDisplayAndRotation(): SysUICutoutInformation? {
         val display = context.display
         val displayCutout: DisplayCutout = display.cutout ?: return null
+        return SysUICutoutInformation(displayCutout, getCameraProtectionForDisplay(display))
+    }
+
+    private fun getCameraProtectionForDisplay(display: Display): CameraProtectionInfo? {
         val displayUniqueId: String? = display.uniqueId
         if (displayUniqueId.isNullOrEmpty()) {
-            return SysUICutoutInformation(displayCutout, cameraProtection = null)
+            return null
         }
-        val cameraProtection: CameraProtectionInfo? =
+        val cameraProtection: CameraProtectionInfo =
             cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
-        return SysUICutoutInformation(displayCutout, cameraProtection)
+                ?: return null
+        val adjustedBoundsForRotation =
+            calculateCameraProtectionBoundsForRotation(display, cameraProtection.bounds)
+        return cameraProtection.copy(bounds = adjustedBoundsForRotation)
+    }
+
+    private fun calculateCameraProtectionBoundsForRotation(
+        display: Display,
+        originalProtectionBounds: Rect,
+    ): Rect {
+        val displayNaturalBounds = display.naturalBounds
+        val rotatedBoundsOut = Rect(originalProtectionBounds)
+        RotationUtils.rotateBounds(
+            /* inOutBounds = */ rotatedBoundsOut,
+            /* parentWidth = */ displayNaturalBounds.width(),
+            /* parentHeight = */ displayNaturalBounds.height(),
+            /* rotation = */ display.rotation
+        )
+        return rotatedBoundsOut
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index e0ce3db..c7a47b1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -18,12 +18,14 @@
 
 import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
 
+import static com.android.systemui.Flags.clipboardNoninteractiveOnLockscreen;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
 
 import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
+import android.app.KeyguardManager;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
@@ -57,6 +59,7 @@
     private final Provider<ClipboardOverlayController> mOverlayProvider;
     private final ClipboardToast mClipboardToast;
     private final ClipboardManager mClipboardManager;
+    private final KeyguardManager mKeyguardManager;
     private final UiEventLogger mUiEventLogger;
     private ClipboardOverlay mClipboardOverlay;
 
@@ -65,11 +68,13 @@
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
             ClipboardToast clipboardToast,
             ClipboardManager clipboardManager,
+            KeyguardManager keyguardManager,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager;
+        mKeyguardManager = keyguardManager;
         mUiEventLogger = uiEventLogger;
     }
 
@@ -92,7 +97,9 @@
             return;
         }
 
-        if (!isUserSetupComplete() // user should not access intents from this state
+        // user should not access intents before setup or while device is locked
+        if ((clipboardNoninteractiveOnLockscreen() && mKeyguardManager.isDeviceLocked())
+                || !isUserSetupComplete()
                 || clipData == null // shouldn't happen, but just in case
                 || clipData.getItemCount() == 0) {
             if (shouldShowToast(clipData)) {
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
new file mode 100644
index 0000000..0482bd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display
+
+import android.graphics.Rect
+import android.view.Display
+import android.view.DisplayInfo
+
+val Display.naturalBounds: Rect
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return Rect(
+            /* left = */ 0,
+            /* top = */ 0,
+            /* right = */ outDisplayInfo.naturalWidth,
+            /* bottom = */ outDisplayInfo.naturalHeight
+        )
+    }
+
+val Display.naturalWidth: Int
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return outDisplayInfo.naturalWidth
+    }
+
+val Display.naturalHeight: Int
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return outDisplayInfo.naturalHeight
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 56162ae..e914c50 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -324,9 +324,6 @@
     // TODO(b/254512673): Tracking Bug
     @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
 
-    // TODO(b/263272731): Tracking Bug
-    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
-
     // TODO(b/266157412): Tracking Bug
     val MEDIA_RETAIN_SESSIONS = unreleasedFlag("media_retain_sessions")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ed48848..dc1f33d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -25,6 +25,7 @@
 import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
+import android.view.View.VISIBLE
 import android.view.ViewGroup
 import android.view.ViewGroup.OnHierarchyChangeListener
 import android.view.ViewPropertyAnimator
@@ -66,6 +67,7 @@
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
 import javax.inject.Provider
+import kotlin.math.min
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
@@ -345,7 +347,7 @@
             }
         }
 
-        onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
+        onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
 
         // Views will be added or removed after the call to bind(). This is needed to avoid many
@@ -405,6 +407,7 @@
 
     private class OnLayoutChange(
         private val viewModel: KeyguardRootViewModel,
+        private val childViews: Map<Int, View>,
         private val burnInParams: MutableStateFlow<BurnInParameters>,
     ) : OnLayoutChangeListener {
         override fun onLayoutChange(
@@ -418,7 +421,7 @@
             oldRight: Int,
             oldBottom: Int
         ) {
-            view.findViewById<View>(R.id.nssl_placeholder)?.let { notificationListPlaceholder ->
+            childViews[R.id.nssl_placeholder]?.let { notificationListPlaceholder ->
                 // After layout, ensure the notifications are positioned correctly
                 viewModel.onNotificationContainerBoundsChanged(
                     notificationListPlaceholder.top.toFloat(),
@@ -426,10 +429,34 @@
                 )
             }
 
-            view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView ->
-                burnInParams.update { current -> current.copy(statusViewTop = statusView.top) }
+            burnInParams.update { current ->
+                current.copy(
+                    minViewY =
+                        if (migrateClocksToBlueprint()) {
+                            // To ensure burn-in doesn't enroach the top inset, get the min top Y
+                            childViews.entries.fold(Int.MAX_VALUE) { currentMin, (viewId, view) ->
+                                min(
+                                    currentMin,
+                                    if (!isUserVisible(view)) {
+                                        Int.MAX_VALUE
+                                    } else {
+                                        view.getTop()
+                                    }
+                                )
+                            }
+                        } else {
+                            childViews[R.id.keyguard_status_view]?.top ?: 0
+                        }
+                )
             }
         }
+
+        private fun isUserVisible(view: View): Boolean {
+            return view.id != R.id.burn_in_layer &&
+                view.visibility == VISIBLE &&
+                view.width > 0 &&
+                view.height > 0
+        }
     }
 
     suspend fun bindAodNotifIconVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 6fcbf48..8665aab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -158,9 +158,9 @@
                 val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
                 val translationY =
                     if (Flags.migrateClocksToBlueprint()) {
-                        burnInY
+                        max(params.topInset - params.minViewY, burnInY)
                     } else {
-                        max(params.topInset, params.statusViewTop + burnInY) - params.statusViewTop
+                        max(params.topInset, params.minViewY + burnInY) - params.minViewY
                     }
 
                 BurnInModel(
@@ -194,8 +194,8 @@
     val clockControllerProvider: Provider<ClockController>? = null,
     /** System insets that keyguard needs to stay out of */
     val topInset: Int = 0,
-    /** Status view top, without translation added in */
-    val statusViewTop: Int = 0,
+    /** The min y-value of the visible elements on lockscreen */
+    val minViewY: Int = Int.MAX_VALUE,
     /** The current y translation of the view */
     val translationY: () -> Float? = { null }
 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index a0a77fb..c409028 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -25,10 +25,13 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
@@ -39,6 +42,7 @@
 @Inject
 constructor(
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+    shadeInteractor: ShadeInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
 
@@ -73,11 +77,22 @@
     }
 
     val notificationAlpha: Flow<Float> =
-        transitionAnimation.sharedFlow(
-            duration = 500.milliseconds,
-            onStep = { it },
-            onCancel = { 1f },
-        )
+        combine(
+            shadeInteractor.shadeExpansion.map { it > 0f },
+            shadeInteractor.qsExpansion.map { it > 0f },
+            transitionAnimation.sharedFlow(
+                duration = 500.milliseconds,
+                onStep = { it },
+                onCancel = { 1f },
+            ),
+        ) { isShadeExpanded, isQsExpanded, alpha ->
+            if (isShadeExpanded || isQsExpanded) {
+                // One example of this happening is dragging a notification while pulsing on AOD
+                1f
+            } else {
+                alpha
+            }
+        }
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 8a565fa..03bc935 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -26,8 +26,4 @@
 class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
     /** */
     fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
-
-    /** Check whether the flag for the receiver success state is enabled. */
-    fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
-        featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 6e9485e..416eae1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -266,8 +266,7 @@
         // translation animation.
         bounceAnimator.removeAllUpdateListeners()
         bounceAnimator.cancel()
-        if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
-                mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
+        if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name) {
             rippleController.expandToSuccessState(rippleView, onAnimationEnd)
             animateViewTranslationAndFade(
                 iconContainerView,
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 4bad45f..16aa99e 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
@@ -25,6 +25,8 @@
 import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
 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.MinimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesResourceRepository
 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredBroadcastRepository
 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
@@ -81,6 +83,11 @@
         impl: QSSettingsRestoredBroadcastRepository
     ): QSSettingsRestoredRepository
 
+    @Binds
+    abstract fun provideMinimumTilesRepository(
+        impl: MinimumTilesResourceRepository
+    ): MinimumTilesRepository
+
     companion object {
         /**
          * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
new file mode 100644
index 0000000..3a005c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Provides the minimum number of tiles required in QS. The default number of tiles should be at
+ * least this many.
+ */
+interface MinimumTilesRepository {
+    val minNumberOfTiles: Int
+}
+
+/**
+ * Minimum number of tiles using the corresponding resource. The value will be read once upon
+ * creation, as it's not expected to change.
+ */
+@SysUISingleton
+class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :
+    MinimumTilesRepository {
+    override val minNumberOfTiles: Int =
+        resources.getInteger(R.integer.quick_settings_min_num_tiles)
+}
+
+/** Provides a fixed minimum number of tiles. */
+class MinimumTilesFixedRepository(override val minNumberOfTiles: Int = 0) : MinimumTilesRepository
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 00ea0b5..214e9f0 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
@@ -19,12 +19,12 @@
 import android.annotation.UserIdInt
 import android.content.res.Resources
 import android.util.SparseArray
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.RetailModeRepository
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -68,6 +68,9 @@
 
     suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>)
 
+    /** Prepend the default list of tiles to the current set of tiles */
+    suspend fun prependDefault(@UserIdInt userId: Int)
+
     companion object {
         /** Position to indicate the end of the list */
         const val POSITION_AT_END = -1
@@ -152,6 +155,12 @@
             ?.reconcileRestore(restoreData, currentAutoAdded)
     }
 
+    override suspend fun prependDefault(
+        userId: Int,
+    ) {
+        userTileRepositories.get(userId)?.prependDefault()
+    }
+
     companion object {
         private const val DELIMITER = TilesSettingConverter.DELIMITER
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 152fd0f..8ad5cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -50,9 +50,8 @@
     private val defaultTiles: List<TileSpec>
         get() = defaultTilesRepository.defaultTiles
 
-    private val changeEvents = MutableSharedFlow<ChangeAction>(
-        extraBufferCapacity = CHANGES_BUFFER_SIZE
-    )
+    private val changeEvents =
+        MutableSharedFlow<ChangeAction>(extraBufferCapacity = CHANGES_BUFFER_SIZE)
 
     private lateinit var _tiles: StateFlow<List<TileSpec>>
 
@@ -163,6 +162,10 @@
         changeEvents.emit(RestoreTiles(restoreData, currentAutoAdded))
     }
 
+    suspend fun prependDefault() {
+        changeEvents.emit(PrependDefault(defaultTiles))
+    }
+
     sealed interface ChangeAction {
         fun apply(currentTiles: List<TileSpec>): List<TileSpec>
     }
@@ -199,6 +202,12 @@
         }
     }
 
+    private data class PrependDefault(val defaultTiles: List<TileSpec>) : ChangeAction {
+        override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+            return defaultTiles + currentTiles
+        }
+    }
+
     private data class RestoreTiles(
         val restoreData: RestoreData,
         val currentAutoAdded: Set<TileSpec>,
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 957cb1e..61896f0 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
@@ -35,6 +35,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.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository
 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.QSPipelineFlagsRepository
@@ -131,6 +132,7 @@
     private val tileSpecRepository: TileSpecRepository,
     private val installedTilesComponentRepository: InstalledTilesComponentRepository,
     private val userRepository: UserRepository,
+    private val minimumTilesRepository: MinimumTilesRepository,
     private val customTileStatePersister: CustomTileStatePersister,
     private val newQSTileFactory: Lazy<NewQSTileFactory>,
     private val tileFactory: QSFactory,
@@ -255,17 +257,23 @@
                         val resolvedSpecs = newTileMap.keys.toList()
                         specsToTiles.clear()
                         specsToTiles.putAll(newTileMap)
-                        _currentSpecsAndTiles.value =
+                        val newResolvedTiles =
                             newTileMap
                                 .filter { it.value is TileOrNotInstalled.Tile }
                                 .map {
                                     TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
                                 }
+
+                        _currentSpecsAndTiles.value = newResolvedTiles
                         logger.logTilesNotInstalled(
                             newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
                             newUser
                         )
-                        if (resolvedSpecs != newTileList) {
+                        if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) {
+                            // We ended up with not enough tiles (some may be not installed).
+                            // Prepend the default set of tiles
+                            launch { tileSpecRepository.prependDefault(currentUser.value) }
+                        } else if (resolvedSpecs != newTileList) {
                             // There were some tiles that couldn't be created. Change the value in
                             // the
                             // repository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 77e146b..5191053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -91,10 +91,10 @@
                     if (!sceneContainerFlags.flexiNotifsEnabled()) {
                         launch {
                             // Only temporarily needed, until flexi notifs go live
-                            viewModel.shadeCollpaseFadeIn.collect { fadeIn ->
+                            viewModel.shadeCollapseFadeIn.collect { fadeIn ->
                                 if (fadeIn) {
                                     android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
-                                        duration = 350
+                                        duration = 250
                                         addUpdateListener { animation ->
                                             controller.setMaxAlphaForExpansion(
                                                 animation.getAnimatedFraction()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 6b949a3..052e35c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -133,6 +133,21 @@
             .distinctUntilChanged()
             .onStart { emit(false) }
 
+    /**
+     * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
+     * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
+     * before the other.
+     */
+    private val isShadeLocked: Flow<Boolean> =
+        combine(
+                keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
+                shadeInteractor.qsExpansion.map { it > 0f },
+                shadeInteractor.shadeExpansion.map { it > 0f },
+            ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
+                isShadeLocked && (isQsExpanded || isShadeExpanded)
+            }
+            .distinctUntilChanged()
+
     val shadeCollapseFadeInComplete = MutableStateFlow(false)
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -190,7 +205,7 @@
             )
 
     /** Are we purely on the glanceable hub without the shade/qs? */
-    internal val isOnGlanceableHubWithoutShade: Flow<Boolean> =
+    val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
                 communalInteractor.isIdleOnCommunal,
                 // Shade with notifications
@@ -208,12 +223,12 @@
             )
 
     /** Fade in only for use after the shade collapses */
-    val shadeCollpaseFadeIn: Flow<Boolean> =
+    val shadeCollapseFadeIn: Flow<Boolean> =
         flow {
                 while (currentCoroutineContext().isActive) {
                     emit(false)
                     // Wait for shade to be fully expanded
-                    keyguardInteractor.statusBarState.first { it == SHADE_LOCKED }
+                    isShadeLocked.first { it }
                     // ... and then for it to be collapsed
                     isOnLockscreenWithoutShade.first { it }
                     emit(true)
@@ -330,16 +345,16 @@
                 // shade expansion or swipe to dismiss
                 combineTransform(
                     isOnLockscreenWithoutShade,
-                    shadeCollpaseFadeIn,
+                    shadeCollapseFadeIn,
                     alphaForShadeAndQsExpansion,
                     keyguardInteractor.dismissAlpha,
                 ) {
                     isOnLockscreenWithoutShade,
-                    shadeCollpaseFadeIn,
+                    shadeCollapseFadeIn,
                     alphaForShadeAndQsExpansion,
                     dismissAlpha ->
                     if (isOnLockscreenWithoutShade) {
-                        if (!shadeCollpaseFadeIn && dismissAlpha != null) {
+                        if (!shadeCollapseFadeIn && dismissAlpha != null) {
                             emit(dismissAlpha)
                         }
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 323ab80..613efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -185,7 +185,7 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
             val displayCutout = sysUICutout?.cutout
             val key = getCacheKey(rotation, displayCutout)
 
@@ -227,7 +227,7 @@
      */
     @JvmOverloads
     fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
-        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
         val displayCutout = sysUICutout?.cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key]
@@ -528,7 +528,7 @@
     var leftMargin = minLeft
     var rightMargin = minRight
     for (cutoutRect in cutoutRects) {
-        val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+        val protectionRect = sysUICutout.cameraProtection?.bounds
         val actualCutoutRect =
             if (protectionRect?.intersects(cutoutRect) == true) {
                 rectUnion(cutoutRect, protectionRect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 1c33d3f..bef6b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -18,15 +18,22 @@
 
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
@@ -42,25 +49,44 @@
     interactor: DeviceBasedSatelliteInteractor,
     @Application scope: CoroutineScope,
     airplaneModeRepository: AirplaneModeRepository,
+    @OemSatelliteInputLog logBuffer: LogBuffer,
 ) {
-    private val shouldShowIcon: StateFlow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { allOos ->
-                if (!allOos) {
-                    flowOf(false)
+    private val shouldShowIcon: Flow<Boolean> =
+        interactor.areAllConnectionsOutOfService.flatMapLatest { allOos ->
+            if (!allOos) {
+                flowOf(false)
+            } else {
+                combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
+                    isSatelliteAllowed,
+                    isAirplaneMode ->
+                    isSatelliteAllowed && !isAirplaneMode
+                }
+            }
+        }
+
+    // This adds a 10 seconds delay before showing the icon
+    private val shouldActuallyShowIcon: StateFlow<Boolean> =
+        shouldShowIcon
+            .distinctUntilChanged()
+            .flatMapLatest { shouldShow ->
+                if (shouldShow) {
+                    logBuffer.log(
+                        TAG,
+                        LogLevel.INFO,
+                        { long1 = DELAY_DURATION.inWholeSeconds },
+                        { "Waiting $long1 seconds before showing the satellite icon" }
+                    )
+                    delay(DELAY_DURATION)
+                    flowOf(true)
                 } else {
-                    combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
-                        isSatelliteAllowed,
-                        isAirplaneMode ->
-                        isSatelliteAllowed && !isAirplaneMode
-                    }
+                    flowOf(false)
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     val icon: StateFlow<Icon?> =
         combine(
-                shouldShowIcon,
+                shouldActuallyShowIcon,
                 interactor.connectionState,
                 interactor.signalStrength,
             ) { shouldShow, state, signalStrength ->
@@ -71,4 +97,9 @@
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    companion object {
+        private const val TAG = "DeviceBasedSatelliteViewModel"
+        private val DELAY_DURATION = 10.seconds
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index a19a0c7..d2a17c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -89,7 +89,7 @@
         loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
 
     private fun CameraProtectionInfo.toTestableVersion() =
-        TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
+        TestableProtectionInfo(logicalCameraId, physicalCameraId, bounds, displayUniqueId)
 
     /**
      * "Testable" version, because the original version contains a Path property, which doesn't
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
index f769b4e..6cb77cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.graphics.Rect
 import com.android.systemui.res.R
 
 class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
@@ -36,7 +37,10 @@
         addInnerCameraProtection()
     }
 
-    fun addOuterCameraProtection(displayUniqueId: String = "111") {
+    fun addOuterCameraProtection(
+        displayUniqueId: String = "111",
+        bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+    ) {
         context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedPhysicalCameraId,
@@ -44,7 +48,7 @@
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_frontBuiltInDisplayCutoutProtection,
-            "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+            bounds.asPath(),
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedScreenUniqueId,
@@ -52,7 +56,10 @@
         )
     }
 
-    fun addInnerCameraProtection(displayUniqueId: String = "222") {
+    fun addInnerCameraProtection(
+        displayUniqueId: String = "222",
+        bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+    ) {
         context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedInnerPhysicalCameraId,
@@ -60,11 +67,13 @@
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_innerBuiltInDisplayCutoutProtection,
-            "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+            bounds.asPath(),
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedInnerScreenUniqueId,
             displayUniqueId
         )
     }
+
+    private fun Rect.asPath() = "M $left, $top H $right V $bottom H $left Z"
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
index f37c4ae..61c7e1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui
 
+import android.graphics.Rect
 import android.view.Display
 import android.view.DisplayAdjustments
 import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -39,7 +44,7 @@
         val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
         val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()
 
         assertThat(sysUICutout).isNull()
     }
@@ -50,7 +55,7 @@
         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
         val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
     }
@@ -61,7 +66,7 @@
         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
         val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -72,7 +77,7 @@
         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
         val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNotNull()
     }
@@ -83,7 +88,7 @@
         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
         val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -94,7 +99,7 @@
         val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
         val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -105,20 +110,170 @@
         val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
         val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
 
+    @Test
+    fun cutoutInfo_rotation0_returnsOriginalProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_0,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(
+                Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+    }
+
+    @Test
+    fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_90,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(Rect(/* left = */ 10, /* top = */ 10, /* right = */ 110, /* bottom = */ 60))
+    }
+
+    @Test
+    fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() {
+        val displayNaturalWidth = 500
+        val displayNaturalHeight = 1000
+        val originalProtectionBounds =
+            Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+        // Safe copy as we don't know at which layer the mutation could happen
+        val originalProtectionBoundsCopy = Rect(originalProtectionBounds)
+        val display =
+            createDisplay(
+                uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+                rotation = Surface.ROTATION_180,
+                width = displayNaturalWidth,
+                height = displayNaturalHeight,
+            )
+        fakeProtectionLoader.addOuterCameraProtection(
+            displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+            bounds = originalProtectionBounds
+        )
+        val provider =
+            SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+
+        // Here we get the rotated bounds once
+        provider.cutoutInfoForCurrentDisplayAndRotation()
+
+        // Rotate display back to original rotation
+        whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+
+        // Now the bounds should match the original ones. We are checking for mutation since Rect
+        // is mutable and has many methods that mutate the instance, and it is easy to do it by
+        // mistake.
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+        assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy)
+    }
+
+    @Test
+    fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_180,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(Rect(/* left = */ 10, /* top = */ 890, /* right = */ 60, /* bottom = */ 990))
+    }
+
+    @Test
+    fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_270,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(
+                Rect(/* left = */ 890, /* top = */ 440, /* right = */ 990, /* bottom = */ 490)
+            )
+    }
+
+    private fun setUpProviderWithCameraProtection(
+        displayWidth: Int,
+        displayHeight: Int,
+        rotation: Int = Surface.ROTATION_0,
+        protectionBounds: Rect,
+    ): SysUICutoutProvider {
+        val display =
+            createDisplay(
+                uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+                rotation = rotation,
+                width =
+                    if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+                        displayWidth
+                    } else {
+                        displayHeight
+                    },
+                height =
+                    if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
+                        displayHeight
+                    else displayWidth,
+            )
+        fakeProtectionLoader.addOuterCameraProtection(
+            displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+            bounds = protectionBounds
+        )
+        return SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+    }
+
     companion object {
         private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
         private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
 
         private fun createDisplay(
+            width: Int = 500,
+            height: Int = 1000,
+            @Rotation rotation: Int = Surface.ROTATION_0,
             uniqueId: String? = "uniqueId",
             cutout: DisplayCutout? = mock<DisplayCutout>()
         ) =
             mock<Display> {
+                whenever(this.getDisplayInfo(any())).thenAnswer {
+                    val displayInfo = it.arguments[0] as DisplayInfo
+                    displayInfo.rotation = rotation
+                    displayInfo.logicalWidth = width
+                    displayInfo.logicalHeight = height
+                    return@thenAnswer true
+                }
+                // Setting width and height to smaller values to simulate real behavior of this API
+                // not always returning the real display size
+                whenever(this.width).thenReturn(width - 5)
+                whenever(this.height).thenReturn(height - 10)
+                whenever(this.rotation).thenReturn(rotation)
                 whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
                 whenever(this.cutout).thenReturn(cutout)
                 whenever(this.uniqueId).thenReturn(uniqueId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 1851582..c65a117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -27,16 +27,20 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -59,6 +63,8 @@
     @Mock
     private ClipboardManager mClipboardManager;
     @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
     private ClipboardToast mClipboardToast;
@@ -96,7 +102,7 @@
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
         mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
-                mClipboardToast, mClipboardManager, mUiEventLogger);
+                mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
     }
 
 
@@ -191,6 +197,34 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
+    public void test_deviceLocked_showsToast() {
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+        verify(mClipboardToast, times(1)).showCopiedToast();
+        verifyZeroInteractions(mOverlayControllerProvider);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
+    public void test_deviceLocked_legacyBehavior_showsInteractiveUI() {
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
+        verifyZeroInteractions(mClipboardToast);
+    }
+
+    @Test
     public void test_nullClipData_showsNothing() {
         when(mClipboardManager.getPrimaryClip()).thenReturn(null);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index e2be4cb..27f59d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -102,7 +102,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
-        whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
 
         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
         whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index f53fc46..cd0652e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -28,7 +29,10 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.mockito.MockitoAnnotations
@@ -61,6 +65,7 @@
                 interactor,
                 testScope.backgroundScope,
                 airplaneModeRepository,
+                FakeLogBuffer.Factory.create(),
             )
     }
 
@@ -121,8 +126,9 @@
             assertThat(latest).isNull()
         }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun icon_satelliteIsOff() =
+    fun icon_satelliteIsOn() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -133,7 +139,45 @@
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = false
 
-            // THEN icon is null because we have service
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // Wait for delay to be completed
+            advanceTimeBy(10.seconds)
+
+            // THEN icon is set because we don't have service
             assertThat(latest).isInstanceOf(Icon::class.java)
         }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun icon_hysteresisWhenEnablingIcon() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null because of the hysteresis
+            assertThat(latest).isNull()
+
+            // Wait for delay to be completed
+            advanceTimeBy(10.seconds)
+
+            // THEN icon is set after the delay
+            assertThat(latest).isInstanceOf(Icon::class.java)
+
+            // GIVEN apm is enabled
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            // THEN icon is null immediately
+            assertThat(latest).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index 733340c..460913f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -22,11 +22,13 @@
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
     AodToLockscreenTransitionViewModel(
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+        shadeInteractor = shadeInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
new file mode 100644
index 0000000..ced29cc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+class FakeDefaultTilesRepository(override val defaultTiles: List<TileSpec> = emptyList()) :
+    DefaultTilesRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index ae4cf3a..a9cce69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -23,7 +23,9 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-class FakeTileSpecRepository : TileSpecRepository {
+class FakeTileSpecRepository(
+    private val defaultTilesRepository: DefaultTilesRepository = FakeDefaultTilesRepository()
+) : TileSpecRepository {
 
     private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
 
@@ -67,4 +69,8 @@
             value = UserTileSpecRepository.reconcileTiles(value, currentAutoAdded, restoreData)
         }
     }
+
+    override suspend fun prependDefault(userId: Int) {
+        with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles + value }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 0091482..604c16f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -18,7 +18,17 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() }
+/** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */
+var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) }
+val Kosmos.minimumTilesRepository: MinimumTilesRepository by
+    Kosmos.Fixture { fakeMinimumTilesRepository }
+
+var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
+val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+    Kosmos.Fixture { fakeDefaultTilesRepository }
+
+val Kosmos.fakeTileSpecRepository by
+    Kosmos.Fixture { FakeTileSpecRepository(defaultTilesRepository) }
 var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository }
 
 val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index 67df563..9ef44c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.qs.newQSTileFactory
 import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
 import com.android.systemui.qs.pipeline.shared.logging.qsLogger
 import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
@@ -37,6 +38,7 @@
             tileSpecRepository,
             installedTilesRepository,
             userRepository,
+            minimumTilesRepository,
             customTileStatePersister,
             { newQSTileFactory },
             qsTileFactory,
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index c9805c7..09a177a 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -149,7 +149,7 @@
                     && mAdaptiveHapticsScales.size() > 0
                     && mAdaptiveHapticsScales.contains(usageHint)) {
                 float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
-                segment = segment.scale(adaptiveScale);
+                segment = segment.scaleLinearly(adaptiveScale);
             }
 
             segments.set(i, segment);