Merge "Show AppSelectorActivity for all users" into main
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 821034a..c673d58 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2797,6 +2797,8 @@
         public int developmentInstallFlags = 0;
         /** {@hide} */
         public int unarchiveId = -1;
+        /** {@hide} */
+        public @Nullable String dexoptCompilerFilter = null;
 
         private final ArrayMap<String, Integer> mPermissionStates;
 
@@ -2850,6 +2852,7 @@
             applicationEnabledSettingPersistent = source.readBoolean();
             developmentInstallFlags = source.readInt();
             unarchiveId = source.readInt();
+            dexoptCompilerFilter = source.readString();
         }
 
         /** {@hide} */
@@ -2885,6 +2888,7 @@
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
             ret.developmentInstallFlags = developmentInstallFlags;
             ret.unarchiveId = unarchiveId;
+            ret.dexoptCompilerFilter = dexoptCompilerFilter;
             return ret;
         }
 
@@ -3564,6 +3568,11 @@
         }
 
         /** @hide */
+        public void setDexoptCompilerFilter(@Nullable String dexoptCompilerFilter) {
+            this.dexoptCompilerFilter = dexoptCompilerFilter;
+        }
+
+        /** @hide */
         @NonNull
         public ArrayMap<String, Integer> getPermissionStates() {
             return mPermissionStates;
@@ -3622,6 +3631,7 @@
                     applicationEnabledSettingPersistent);
             pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
             pw.printPair("unarchiveId", unarchiveId);
+            pw.printPair("dexoptCompilerFilter", dexoptCompilerFilter);
             pw.println();
         }
 
@@ -3667,6 +3677,7 @@
             dest.writeBoolean(applicationEnabledSettingPersistent);
             dest.writeInt(developmentInstallFlags);
             dest.writeInt(unarchiveId);
+            dest.writeString(dexoptCompilerFilter);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index de26384..4819f67 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2360,11 +2360,8 @@
      * <p>If the session configuration is not supported, the AE mode reported in the
      * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
      * <p>When this AE mode is enabled, the CaptureResult field
-     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will be present and not null. Otherwise, the
-     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} field will not be present in the CaptureResult.</p>
-     * <p>The application can observe the CaptureResult field
-     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or
-     * 'INACTIVE'.</p>
+     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will indicate when low light boost is 'ACTIVE'
+     * or 'INACTIVE'. By default {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will be 'INACTIVE'.</p>
      * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
      * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.
      * This mode will be 'INACTIVE' once the scene lighting condition is greater than the
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ef83f9a..d652b4c 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2819,12 +2819,11 @@
      * <p>When low light boost is enabled by setting the AE mode to
      * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
      * boost when the light level threshold is exceeded.</p>
-     * <p>This field is present in the CaptureResult when the AE mode is set to
-     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'. Otherwise, the field is not present.</p>
      * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
      * indicate when it is not being applied by returning 'INACTIVE'.</p>
      * <p>This key will be absent from the CaptureResult if AE mode is not set to
      * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
+     * <p>The default value will always be 'INACTIVE'.</p>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE INACTIVE}</li>
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4d69437..943b04f 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -401,10 +401,7 @@
     private long mStylusHwSessionsTimeout = STYLUS_HANDWRITING_IDLE_TIMEOUT_MS;
     private Runnable mStylusWindowIdleTimeoutRunnable;
     private long mStylusWindowIdleTimeoutForTest;
-    /**
-     * Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
-     **/
-    private int mLastUsedToolType;
+
     /**
      * Tracks the ctrl+shift shortcut
      **/
@@ -1368,7 +1365,6 @@
 
     private void updateEditorToolTypeInternal(int toolType) {
         if (Flags.useHandwritingListenerForTooltype()) {
-            mLastUsedToolType = toolType;
             if (mInputEditorInfo != null) {
                 mInputEditorInfo.setInitialToolType(toolType);
             }
@@ -3385,9 +3381,6 @@
                 null /* icProto */);
         mInputStarted = true;
         mStartedInputConnection = ic;
-        if (Flags.useHandwritingListenerForTooltype()) {
-            editorInfo.setInitialToolType(mLastUsedToolType);
-        }
         mInputEditorInfo = editorInfo;
         initialize();
         mInlineSuggestionSessionController.notifyOnStartInput(
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 90f37c0..d32486c 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -203,44 +203,31 @@
     return true;
 }
 
-static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj,
-                                  PointerCoords& outRawPointerCoords) {
-    outRawPointerCoords.clear();
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.x));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.y));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.pressure));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SIZE,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.size));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.touchMajor));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.touchMinor));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.toolMajor));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.toolMinor));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.orientation));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.relativeX));
-    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y,
-                                     env->GetFloatField(pointerCoordsObj,
-                                                        gPointerCoordsClassInfo.relativeY));
-    outRawPointerCoords.isResampled =
-            env->GetBooleanField(pointerCoordsObj, gPointerCoordsClassInfo.isResampled);
+static PointerCoords pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj) {
+    PointerCoords out{};
+    out.setAxisValue(AMOTION_EVENT_AXIS_X,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.x));
+    out.setAxisValue(AMOTION_EVENT_AXIS_Y,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.y));
+    out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.pressure));
+    out.setAxisValue(AMOTION_EVENT_AXIS_SIZE,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.size));
+    out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.touchMajor));
+    out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.touchMinor));
+    out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.toolMajor));
+    out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.toolMinor));
+    out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.orientation));
+    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.relativeX));
+    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y,
+                     env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.relativeY));
+    out.isResampled = env->GetBooleanField(pointerCoordsObj, gPointerCoordsClassInfo.isResampled);
 
     BitSet64 bits =
             BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits));
@@ -254,13 +241,14 @@
             uint32_t index = 0;
             do {
                 uint32_t axis = bits.clearFirstMarkedBit();
-                outRawPointerCoords.setAxisValue(axis, values[index++]);
+                out.setAxisValue(axis, values[index++]);
             } while (!bits.isEmpty());
 
             env->ReleasePrimitiveArrayCritical(valuesArray, values, JNI_ABORT);
             env->DeleteLocalRef(valuesArray);
         }
     }
+    return out;
 }
 
 static jfloatArray obtainPackedAxisValuesArray(JNIEnv* env, uint32_t minSize,
@@ -312,14 +300,13 @@
     env->SetLongField(outPointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits, outBits);
 }
 
-static void pointerPropertiesToNative(JNIEnv* env, jobject pointerPropertiesObj,
-        PointerProperties* outPointerProperties) {
-    outPointerProperties->clear();
-    outPointerProperties->id = env->GetIntField(pointerPropertiesObj,
-            gPointerPropertiesClassInfo.id);
-    const int32_t toolType = env->GetIntField(pointerPropertiesObj,
-            gPointerPropertiesClassInfo.toolType);
-    outPointerProperties->toolType = static_cast<ToolType>(toolType);
+static PointerProperties pointerPropertiesToNative(JNIEnv* env, jobject pointerPropertiesObj) {
+    PointerProperties out{};
+    out.id = env->GetIntField(pointerPropertiesObj, gPointerPropertiesClassInfo.id);
+    const int32_t toolType =
+            env->GetIntField(pointerPropertiesObj, gPointerPropertiesClassInfo.toolType);
+    out.toolType = static_cast<ToolType>(toolType);
+    return out;
 }
 
 static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* pointerProperties,
@@ -356,15 +343,17 @@
     transform.set(xOffset, yOffset);
     const ui::Transform inverseTransform = transform.inverse();
 
-    PointerProperties pointerProperties[pointerCount];
-    PointerCoords rawPointerCoords[pointerCount];
+    std::vector<PointerProperties> pointerProperties;
+    pointerProperties.reserve(pointerCount);
+    std::vector<PointerCoords> rawPointerCoords;
+    rawPointerCoords.reserve(pointerCount);
 
     for (jint i = 0; i < pointerCount; i++) {
         jobject pointerPropertiesObj = env->GetObjectArrayElement(pointerPropertiesObjArray, i);
         if (!pointerPropertiesObj) {
             return 0;
         }
-        pointerPropertiesToNative(env, pointerPropertiesObj, &pointerProperties[i]);
+        pointerProperties.emplace_back(pointerPropertiesToNative(env, pointerPropertiesObj));
         env->DeleteLocalRef(pointerPropertiesObj);
 
         jobject pointerCoordsObj = env->GetObjectArrayElement(pointerCoordsObjArray, i);
@@ -372,13 +361,13 @@
             jniThrowNullPointerException(env, "pointerCoords");
             return 0;
         }
-        pointerCoordsToNative(env, pointerCoordsObj, rawPointerCoords[i]);
-        if (rawPointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION) != 0.f) {
+        rawPointerCoords.emplace_back(pointerCoordsToNative(env, pointerCoordsObj));
+        PointerCoords& coords = rawPointerCoords.back();
+        if (coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION) != 0.f) {
             flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
                     AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
         }
-        MotionEvent::calculateTransformedCoordsInPlace(rawPointerCoords[i], source, flags,
-                                                       inverseTransform);
+        MotionEvent::calculateTransformedCoordsInPlace(coords, source, flags, inverseTransform);
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
@@ -388,7 +377,8 @@
                       static_cast<MotionClassification>(classification), transform, xPrecision,
                       yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, kIdentityTransform, downTimeNanos,
-                      eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords);
+                      eventTimeNanos, pointerCount, pointerProperties.data(),
+                      rawPointerCoords.data());
 
     return reinterpret_cast<jlong>(event.release());
 }
@@ -410,7 +400,8 @@
 
     const ui::Transform inverseTransform = event->getTransform().inverse();
 
-    PointerCoords rawPointerCoords[pointerCount];
+    std::vector<PointerCoords> rawPointerCoords;
+    rawPointerCoords.reserve(pointerCount);
 
     for (size_t i = 0; i < pointerCount; i++) {
         jobject pointerCoordsObj = env->GetObjectArrayElement(pointerCoordsObjArray, i);
@@ -418,13 +409,13 @@
             jniThrowNullPointerException(env, "pointerCoords");
             return;
         }
-        pointerCoordsToNative(env, pointerCoordsObj, rawPointerCoords[i]);
-        MotionEvent::calculateTransformedCoordsInPlace(rawPointerCoords[i], event->getSource(),
+        rawPointerCoords.emplace_back(pointerCoordsToNative(env, pointerCoordsObj));
+        MotionEvent::calculateTransformedCoordsInPlace(rawPointerCoords.back(), event->getSource(),
                                                        event->getFlags(), inverseTransform);
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
-    event->addSample(eventTimeNanos, rawPointerCoords);
+    event->addSample(eventTimeNanos, rawPointerCoords.data());
     event->setMetaState(event->getMetaState() | metaState);
 }
 
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 5d52da1..06cb0ee 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1224,11 +1224,8 @@
         // Infrequent update
         Thread.sleep(delay);
 
-        // Even though this is not a small View, step 3 is triggered by this flag, which
-        // brings intermittent to LOW
-        int intermittentExpected = toolkitFrameRateBySizeReadOnly()
-                ? FRAME_RATE_CATEGORY_LOW
-                : FRAME_RATE_CATEGORY_NORMAL;
+        // The expected category is normal for intermittent.
+        int intermittentExpected = FRAME_RATE_CATEGORY_NORMAL;
 
         sInstrumentation.runOnMainSync(() -> {
             mView.invalidate();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 835f1af..07082a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -53,7 +53,7 @@
 
     private final ShellExecutor mMainExecutor;
 
-    private boolean mIsActivityLetterboxed;
+    private boolean mIsLetterboxDoubleTapEnabled;
 
     private int mLetterboxVerticalPosition;
 
@@ -91,7 +91,7 @@
             Function<Integer, Integer> disappearTimeSupplier) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
-        mIsActivityLetterboxed = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
+        mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
         mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
         mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
         mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
@@ -119,7 +119,7 @@
 
     @Override
     protected boolean eligibleToShowLayout() {
-        return mIsActivityLetterboxed
+        return mIsLetterboxDoubleTapEnabled
                 && (mLetterboxVerticalPosition != -1 || mLetterboxHorizontalPosition != -1);
     }
 
@@ -142,13 +142,13 @@
     @Override
     public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
             boolean canShow) {
-        final boolean prevIsActivityLetterboxed = mIsActivityLetterboxed;
+        final boolean prevIsLetterboxDoubleTapEnabled = mIsLetterboxDoubleTapEnabled;
         final int prevLetterboxVerticalPosition = mLetterboxVerticalPosition;
         final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition;
         final int prevTopActivityLetterboxWidth = mTopActivityLetterboxWidth;
         final int prevTopActivityLetterboxHeight = mTopActivityLetterboxHeight;
         final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
-        mIsActivityLetterboxed = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
+        mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
         mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
         mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
         mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
@@ -162,7 +162,7 @@
         mHasLetterboxSizeChanged = prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
                 || prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight;
 
-        if (mHasUserDoubleTapped || prevIsActivityLetterboxed != mIsActivityLetterboxed
+        if (mHasUserDoubleTapped || prevIsLetterboxDoubleTapEnabled != mIsLetterboxDoubleTapEnabled
                 || prevLetterboxVerticalPosition != mLetterboxVerticalPosition
                 || prevLetterboxHorizontalPosition != mLetterboxHorizontalPosition
                 || prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
@@ -249,7 +249,7 @@
                     && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
                         || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
 
-        if (mIsActivityLetterboxed && (eligibleForDisplayHorizontalEducation
+        if (mIsLetterboxDoubleTapEnabled && (eligibleForDisplayHorizontalEducation
                 || eligibleForDisplayVerticalEducation)) {
             int availableWidth = getTaskBounds().width() - mTopActivityLetterboxWidth;
             int availableHeight = getTaskBounds().height() - mTopActivityLetterboxHeight;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 0713a79..9192e6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -187,7 +187,10 @@
             KEYBOARD_SHORTCUT_ENTER(
                 FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER
             ),
-            SCREEN_ON(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON)
+            SCREEN_ON(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON),
+            APP_FROM_OVERVIEW(
+                FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_FROM_OVERVIEW
+            ),
         }
 
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 075e3ae..cee2d92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -314,8 +314,7 @@
             WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
             Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
             TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> EnterReason.APP_HANDLE_MENU_BUTTON
-            // TODO(b/344822506): Create and update EnterReason to APP_FROM_OVERVIEW
-            TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> EnterReason.UNKNOWN_ENTER
+            TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> EnterReason.APP_FROM_OVERVIEW
             TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> EnterReason.KEYBOARD_SHORTCUT_ENTER
             WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
             else -> EnterReason.UNKNOWN_ENTER
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 7122181..5f6132a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -187,7 +187,6 @@
     }
 
     @Test
-    // TODO(b/344822506): Update test when we add enter reason for app from overview
     fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonUnknown() {
         val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
         val transitionInfo =
@@ -200,7 +199,7 @@
 
         assertThat(sessionId).isNotNull()
         verify(desktopModeEventLogger, times(1))
-            .logSessionEnter(eq(sessionId!!), eq(EnterReason.UNKNOWN_ENTER))
+            .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
         verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
         verifyZeroInteractions(desktopModeEventLogger)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 4c8c113..c48ced1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dreams;
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -148,6 +149,8 @@
         when(mDreamOverlayContainerView.getRootSurfaceControl())
                 .thenReturn(mAttachedSurfaceControl);
         when(mKeyguardTransitionInteractor.isFinishedInStateWhere(any())).thenReturn(emptyFlow());
+        when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(false));
+        when(mCommunalInteractor.isCommunalShowing()).thenReturn(MutableStateFlow(false));
 
         mController = new DreamOverlayContainerViewController(
                 mDreamOverlayContainerView,
@@ -330,4 +333,12 @@
         mController.onViewDetached();
         verify(mBouncerlessScrimController).removeCallback(any());
     }
+
+    @EnableFlags(android.service.dreams.Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED)
+    @Test
+    public void testOnViewAttachedSucceedsWhenDreamHandlesBeingObscuredFlagEnabled() {
+        // This test will catch failures in presubmit when the dream_handles_being_obscured flag is
+        // enabled.
+        mController.onViewAttached();
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
index 460a1fc..b0959e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -58,6 +59,62 @@
             assertThat(viewModel?.tint).isEqualTo(Color.WHITE)
         }
 
+    @Test
+    fun startsDozing_doNotShowAodVariant() =
+        testScope.runTest {
+            val viewModel by collectLastValue(underTest.viewModel)
+
+            givenUdfpsEnrolledAndEnabled()
+            kosmos.run {
+                fakeKeyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DOZING,
+                    testScope = testScope,
+                    throughTransitionState = TransitionState.STARTED,
+                )
+            }
+
+            assertThat(viewModel?.useAodVariant).isEqualTo(false)
+        }
+
+    @Test
+    fun finishedDozing_showAodVariant() =
+        testScope.runTest {
+            val viewModel by collectLastValue(underTest.viewModel)
+
+            givenUdfpsEnrolledAndEnabled()
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+                throughTransitionState = TransitionState.FINISHED,
+            )
+
+            assertThat(viewModel?.useAodVariant).isEqualTo(true)
+        }
+
+    @Test
+    fun startTransitionToLockscreenFromDozing_doNotShowAodVariant() =
+        testScope.runTest {
+            val viewModel by collectLastValue(underTest.viewModel)
+
+            givenUdfpsEnrolledAndEnabled()
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DOZING,
+                testScope = testScope,
+                throughTransitionState = TransitionState.FINISHED,
+            )
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DOZING,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING,
+            )
+
+            assertThat(viewModel?.useAodVariant).isEqualTo(false)
+        }
+
     private fun givenUdfpsEnrolledAndEnabled() {
         kosmos.fakeFingerprintPropertyRepository.supportsUdfps()
         kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 7a37a9e..73e6506 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -56,11 +56,15 @@
             underTest.addSelectedUserMediaEntry(userMedia)
 
             assertThat(selectedUserEntries?.get(instanceId)).isEqualTo(userMedia)
+            assertThat(underTest.hasActiveMedia()).isTrue()
+            assertThat(underTest.hasAnyMedia()).isTrue()
 
             underTest.addSelectedUserMediaEntry(userMedia.copy(active = false))
 
             assertThat(selectedUserEntries?.get(instanceId)).isNotEqualTo(userMedia)
             assertThat(selectedUserEntries?.get(instanceId)?.active).isFalse()
+            assertThat(underTest.hasActiveMedia()).isFalse()
+            assertThat(underTest.hasAnyMedia()).isTrue()
         }
 
     @Test
@@ -74,8 +78,12 @@
             underTest.addSelectedUserMediaEntry(userMedia)
 
             assertThat(selectedUserEntries?.get(instanceId)).isEqualTo(userMedia)
+            assertThat(underTest.hasActiveMedia()).isTrue()
+            assertThat(underTest.hasAnyMedia()).isTrue()
 
             assertThat(underTest.removeSelectedUserMediaEntry(instanceId, userMedia)).isTrue()
+            assertThat(underTest.hasActiveMedia()).isFalse()
+            assertThat(underTest.hasAnyMedia()).isFalse()
         }
 
     @Test
@@ -145,6 +153,7 @@
 
             assertThat(smartspaceMediaData).isNotEqualTo(mediaRecommendation)
             assertThat(smartspaceMediaData?.isActive).isFalse()
+            assertThat(underTest.isRecommendationActive()).isFalse()
         }
 
     @Test
@@ -349,6 +358,14 @@
                 .inOrder()
         }
 
+    @Test
+    fun hasAnyMedia_noMediaSet_returnsFalse() =
+        testScope.runTest { assertThat(underTest.hasAnyMedia()).isFalse() }
+
+    @Test
+    fun hasActiveMedia_noMediaSet_returnsFalse() =
+        testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() }
+
     private fun createMediaData(
         app: String,
         playing: Boolean,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index 39dbc7e..c62195f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -76,22 +76,20 @@
         testScope.runTest {
             val hasActiveMediaOrRecommendation by
                 collectLastValue(underTest.hasActiveMediaOrRecommendation)
-            val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
-            val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
 
             val userMedia = MediaData(active = true)
 
             mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
-            assertThat(hasActiveMedia).isTrue()
-            assertThat(hasAnyMedia).isTrue()
+            assertThat(underTest.hasActiveMedia()).isTrue()
+            assertThat(underTest.hasAnyMedia()).isTrue()
 
             mediaFilterRepository.addSelectedUserMediaEntry(userMedia.copy(active = false))
 
             assertThat(hasActiveMediaOrRecommendation).isFalse()
-            assertThat(hasActiveMedia).isFalse()
-            assertThat(hasAnyMedia).isTrue()
+            assertThat(underTest.hasActiveMedia()).isFalse()
+            assertThat(underTest.hasAnyMedia()).isTrue()
         }
 
     @Test
@@ -99,8 +97,6 @@
         testScope.runTest {
             val hasActiveMediaOrRecommendation by
                 collectLastValue(underTest.hasActiveMediaOrRecommendation)
-            val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
-            val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
 
             val userMedia = MediaData(active = false)
             val instanceId = userMedia.instanceId
@@ -109,8 +105,8 @@
             mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
 
             assertThat(hasActiveMediaOrRecommendation).isFalse()
-            assertThat(hasActiveMedia).isFalse()
-            assertThat(hasAnyMedia).isTrue()
+            assertThat(underTest.hasActiveMedia()).isFalse()
+            assertThat(underTest.hasAnyMedia()).isTrue()
 
             assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
                 .isTrue()
@@ -119,8 +115,8 @@
             )
 
             assertThat(hasActiveMediaOrRecommendation).isFalse()
-            assertThat(hasActiveMedia).isFalse()
-            assertThat(hasAnyMedia).isFalse()
+            assertThat(underTest.hasActiveMedia()).isFalse()
+            assertThat(underTest.hasAnyMedia()).isFalse()
         }
 
     @Test
@@ -147,6 +143,7 @@
 
             mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
             mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
+            mediaFilterRepository.setOrderedMedia()
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
             assertThat(hasAnyMediaOrRecommendation).isTrue()
@@ -202,7 +199,7 @@
 
     @Test
     fun hasAnyMedia_noMediaSet_returnsFalse() =
-        testScope.runTest { assertThat(underTest.hasAnyMedia.value).isFalse() }
+        testScope.runTest { assertThat(underTest.hasAnyMedia()).isFalse() }
 
     @Test
     fun hasAnyMediaOrRecommendation_noMediaSet_returnsFalse() =
@@ -210,7 +207,7 @@
 
     @Test
     fun hasActiveMedia_noMediaSet_returnsFalse() =
-        testScope.runTest { assertThat(underTest.hasActiveMedia.value).isFalse() }
+        testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() }
 
     @Test
     fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 118ea16..14eb972 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,8 +26,11 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -36,7 +39,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
@@ -46,7 +48,6 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
-import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 
 @ExperimentalCoroutinesApi
 @SysUISingleton
@@ -82,17 +83,16 @@
     }
 
     val surfaceBehindVisibility: Flow<Boolean?> =
-        combine(
-                transitionInteractor.startedKeyguardTransitionStep,
-                transitionInteractor.transition(Edge.create(from = KeyguardState.ALTERNATE_BOUNCER))
-            ) { startedStep, fromBouncerStep ->
-                if (startedStep.to != KeyguardState.GONE) {
-                    return@combine null
-                }
-
+        transitionInteractor
+            .transition(
+                edge = Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = Scenes.Gone),
+                edgeWithoutSceneContainer =
+                    Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE)
+            )
+            .map<TransitionStep, Boolean?> {
                 // The alt bouncer is pretty fast to hide, so start the surface behind animation
                 // around 30%.
-                fromBouncerStep.value > 0.3f
+                it.value > 0.3f
             }
             .onStart {
                 // Default to null ("don't care, use a reasonable default").
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 8cab3cd..f30eef0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -24,16 +24,18 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import java.util.UUID
@@ -59,7 +61,6 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
-    private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
     powerInteractor: PowerInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
@@ -97,14 +98,13 @@
      * LOCKSCREEN is running.
      */
     val surfaceBehindVisibility: Flow<Boolean?> =
-        transitionInteractor.startedKeyguardTransitionStep
-            .map { startedStep ->
-                if (startedStep.to != KeyguardState.GONE) {
-                    // LOCKSCREEN to anything but GONE does not require any special surface
-                    // visibility handling.
-                    return@map null
-                }
-
+        transitionInteractor
+            .transition(
+                edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = Scenes.Gone),
+                edgeWithoutSceneContainer =
+                    Edge.create(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            .map<TransitionStep, Boolean?> {
                 true // Make the surface visible during LS -> GONE transitions.
             }
             .onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index aaf935f..f8208b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -22,15 +22,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
@@ -40,8 +39,9 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
@@ -56,7 +56,6 @@
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
-    private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
     private val selectedUserInteractor: SelectedUserInteractor,
     powerInteractor: PowerInteractor,
@@ -81,24 +80,25 @@
     }
 
     val surfaceBehindVisibility: Flow<Boolean?> =
-        combine(
-                transitionInteractor.startedKeyguardTransitionStep,
-                transitionInteractor.transition(
-                    edge = Edge.create(from = Scenes.Bouncer),
-                    edgeWithoutSceneContainer = Edge.create(from = KeyguardState.PRIMARY_BOUNCER)
+        if (SceneContainerFlag.isEnabled) {
+            // The edge Scenes.Bouncer <-> Scenes.Gone is handled by STL
+            flowOf(null)
+        } else {
+            transitionInteractor
+                .transition(
+                    edge = Edge.INVALID,
+                    edgeWithoutSceneContainer =
+                        Edge.create(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
                 )
-            ) { startedStep, fromBouncerStep ->
-                if (startedStep.to != KeyguardState.GONE) {
-                    return@combine null
+                .map<TransitionStep, Boolean?> {
+                    it.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
                 }
-
-                fromBouncerStep.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
-            }
-            .onStart {
-                // Default to null ("don't care, use a reasonable default").
-                emit(null)
-            }
-            .distinctUntilChanged()
+                .onStart {
+                    // Default to null ("don't care, use a reasonable default").
+                    emit(null)
+                }
+                .distinctUntilChanged()
+        }
 
     fun dismissPrimaryBouncer() {
         scope.launch { startTransitionTo(KeyguardState.GONE) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
index 39f1ebe..aa0a9d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -62,7 +62,7 @@
                 addTransition(
                     clockViewModel.currentClock.value?.let { DefaultClockSteppingTransition(it) }
                 )
-            else -> addTransition(ClockSizeTransition(config, clockViewModel, smartspaceViewModel))
+            else -> addTransition(ClockSizeTransition(config, clockViewModel))
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index f17dbd2..4d914c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -31,8 +31,9 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_UP_MILLIS
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
 import com.google.android.material.math.MathUtils
@@ -46,13 +47,12 @@
 class ClockSizeTransition(
     config: IntraBlueprintTransition.Config,
     clockViewModel: KeyguardClockViewModel,
-    smartspaceViewModel: KeyguardSmartspaceViewModel,
 ) : TransitionSet() {
     init {
         ordering = ORDERING_TOGETHER
         if (config.type != Type.SmartspaceVisibility) {
-            addTransition(ClockFaceOutTransition(config, clockViewModel, smartspaceViewModel))
-            addTransition(ClockFaceInTransition(config, clockViewModel, smartspaceViewModel))
+            addTransition(ClockFaceOutTransition(config, clockViewModel))
+            addTransition(ClockFaceInTransition(config, clockViewModel))
         }
         addTransition(SmartspaceMoveTransition(config, clockViewModel))
     }
@@ -60,8 +60,11 @@
     abstract class VisibilityBoundsTransition() : Transition() {
         abstract val captureSmartspace: Boolean
         protected val TAG = this::class.simpleName!!
+
         override fun captureEndValues(transition: TransitionValues) = captureValues(transition)
+
         override fun captureStartValues(transition: TransitionValues) = captureValues(transition)
+
         override fun getTransitionProperties(): Array<String> = TRANSITION_PROPERTIES
 
         private fun captureValues(transition: TransitionValues) {
@@ -222,21 +225,19 @@
         }
     }
 
-    class ClockFaceInTransition(
+    abstract class ClockFaceTransition(
         config: IntraBlueprintTransition.Config,
         val viewModel: KeyguardClockViewModel,
-        val smartspaceViewModel: KeyguardSmartspaceViewModel,
     ) : VisibilityBoundsTransition() {
-        override val captureSmartspace = !viewModel.isLargeClockVisible.value
+        protected abstract val isLargeClock: Boolean
+        protected abstract val smallClockMoveScale: Float
+        override val captureSmartspace
+            get() = !isLargeClock
 
-        init {
-            duration = CLOCK_IN_MILLIS
-            startDelay = CLOCK_IN_START_DELAY_MILLIS
-            interpolator = CLOCK_IN_INTERPOLATOR
-
-            if (viewModel.isLargeClockVisible.value) {
+        protected fun addTargets() {
+            if (isLargeClock) {
                 viewModel.currentClock.value?.let {
-                    if (DEBUG) Log.i(TAG, "Large Clock In: ${it.largeClock.layout.views}")
+                    if (DEBUG) Log.i(TAG, "Adding large clock views: ${it.largeClock.layout.views}")
                     it.largeClock.layout.views.forEach { addTarget(it) }
                 }
                     ?: run {
@@ -244,7 +245,7 @@
                         addTarget(R.id.lockscreen_clock_view_large)
                     }
             } else {
-                if (DEBUG) Log.i(TAG, "Small Clock In")
+                if (DEBUG) Log.i(TAG, "Adding small clock")
                 addTarget(R.id.lockscreen_clock_view)
             }
         }
@@ -262,89 +263,59 @@
             if (fromIsVis == toIsVis) return
 
             fromBounds.set(toBounds)
-            if (viewModel.isLargeClockVisible.value) {
+            if (isLargeClock) {
                 // Large clock shouldn't move; fromBounds already set
             } else if (toSSBounds != null && fromSSBounds != null) {
                 // Instead of moving the small clock the full distance, we compute the distance
                 // smartspace will move. We then scale this to match the duration of this animation
                 // so that the small clock moves at the same speed as smartspace.
                 val ssTranslation =
-                    abs((toSSBounds.top - fromSSBounds.top) * SMALL_CLOCK_IN_MOVE_SCALE).toInt()
+                    abs((toSSBounds.top - fromSSBounds.top) * smallClockMoveScale).toInt()
                 fromBounds.top = toBounds.top - ssTranslation
                 fromBounds.bottom = toBounds.bottom - ssTranslation
             } else {
                 Log.e(TAG, "mutateBounds: smallClock received no smartspace bounds")
             }
         }
+    }
+
+    class ClockFaceInTransition(
+        config: IntraBlueprintTransition.Config,
+        viewModel: KeyguardClockViewModel,
+    ) : ClockFaceTransition(config, viewModel) {
+        override val isLargeClock = viewModel.isLargeClockVisible.value
+        override val smallClockMoveScale = CLOCK_IN_MILLIS / STATUS_AREA_MOVE_DOWN_MILLIS.toFloat()
+
+        init {
+            duration = CLOCK_IN_MILLIS
+            startDelay = CLOCK_IN_START_DELAY_MILLIS
+            interpolator = CLOCK_IN_INTERPOLATOR
+            addTargets()
+        }
 
         companion object {
             const val CLOCK_IN_MILLIS = 167L
             const val CLOCK_IN_START_DELAY_MILLIS = 133L
             val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
-            const val SMALL_CLOCK_IN_MOVE_SCALE =
-                CLOCK_IN_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_DOWN_MILLIS.toFloat()
         }
     }
 
     class ClockFaceOutTransition(
         config: IntraBlueprintTransition.Config,
-        val viewModel: KeyguardClockViewModel,
-        val smartspaceViewModel: KeyguardSmartspaceViewModel,
-    ) : VisibilityBoundsTransition() {
-        override val captureSmartspace = viewModel.isLargeClockVisible.value
+        viewModel: KeyguardClockViewModel,
+    ) : ClockFaceTransition(config, viewModel) {
+        override val isLargeClock = !viewModel.isLargeClockVisible.value
+        override val smallClockMoveScale = CLOCK_OUT_MILLIS / STATUS_AREA_MOVE_UP_MILLIS.toFloat()
 
         init {
             duration = CLOCK_OUT_MILLIS
             interpolator = CLOCK_OUT_INTERPOLATOR
-
-            if (viewModel.isLargeClockVisible.value) {
-                if (DEBUG) Log.i(TAG, "Small Clock Out")
-                addTarget(R.id.lockscreen_clock_view)
-            } else {
-                viewModel.currentClock.value?.let {
-                    if (DEBUG) Log.i(TAG, "Large Clock Out: ${it.largeClock.layout.views}")
-                    it.largeClock.layout.views.forEach { addTarget(it) }
-                }
-                    ?: run {
-                        Log.e(TAG, "No large clock set, falling back")
-                        addTarget(R.id.lockscreen_clock_view_large)
-                    }
-            }
-        }
-
-        override fun mutateBounds(
-            view: View,
-            fromIsVis: Boolean,
-            toIsVis: Boolean,
-            fromBounds: Rect,
-            toBounds: Rect,
-            fromSSBounds: Rect?,
-            toSSBounds: Rect?
-        ) {
-            // Move normally if clock is not changing visibility
-            if (fromIsVis == toIsVis) return
-
-            toBounds.set(fromBounds)
-            if (!viewModel.isLargeClockVisible.value) {
-                // Large clock shouldn't move; toBounds already set
-            } else if (toSSBounds != null && fromSSBounds != null) {
-                // Instead of moving the small clock the full distance, we compute the distance
-                // smartspace will move. We then scale this to match the duration of this animation
-                // so that the small clock moves at the same speed as smartspace.
-                val ssTranslation =
-                    abs((toSSBounds.top - fromSSBounds.top) * SMALL_CLOCK_OUT_MOVE_SCALE).toInt()
-                toBounds.top = fromBounds.top - ssTranslation
-                toBounds.bottom = fromBounds.bottom - ssTranslation
-            } else {
-                Log.e(TAG, "mutateBounds: smallClock received no smartspace bounds")
-            }
+            addTargets()
         }
 
         companion object {
             const val CLOCK_OUT_MILLIS = 133L
             val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
-            const val SMALL_CLOCK_OUT_MOVE_SCALE =
-                CLOCK_OUT_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_UP_MILLIS.toFloat()
         }
     }
 
@@ -353,15 +324,14 @@
         val config: IntraBlueprintTransition.Config,
         viewModel: KeyguardClockViewModel,
     ) : VisibilityBoundsTransition() {
+        private val isLargeClock = viewModel.isLargeClockVisible.value
         override val captureSmartspace = false
 
         init {
             duration =
-                if (viewModel.isLargeClockVisible.value) STATUS_AREA_MOVE_UP_MILLIS
-                else STATUS_AREA_MOVE_DOWN_MILLIS
+                if (isLargeClock) STATUS_AREA_MOVE_UP_MILLIS else STATUS_AREA_MOVE_DOWN_MILLIS
             interpolator = Interpolators.EMPHASIZED
             addTarget(sharedR.id.date_smartspace_view)
-            addTarget(sharedR.id.weather_smartspace_view)
             addTarget(sharedR.id.bc_smartspace_view)
 
             // Notifications normally and media on split shade needs to be moved
@@ -391,6 +361,6 @@
     }
 
     companion object {
-        val DEBUG = true
+        val DEBUG = false
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 0f1f5c1..06b76b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -52,8 +52,11 @@
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
     private val isShowingAodOrDozing: Flow<Boolean> =
-        transitionInteractor.startedKeyguardState.map { keyguardState ->
-            keyguardState == KeyguardState.AOD || keyguardState == KeyguardState.DOZING
+        combine(
+            transitionInteractor.startedKeyguardState,
+            transitionInteractor.transitionValue(KeyguardState.DOZING),
+        ) { startedKeyguardState, dozingTransitionValue ->
+            startedKeyguardState == KeyguardState.AOD || dozingTransitionValue == 1f
         }
 
     private fun getColor(usingBackgroundProtection: Boolean): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index 7ac03bf..c6efcfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -130,6 +130,6 @@
 
     companion object {
         private const val TAG = "KeyguardBlueprintViewModel"
-        private const val DEBUG = true
+        private const val DEBUG = false
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 0c70f10..220d326 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -218,9 +218,9 @@
                         mediaFromRecPackageName = null
                         _currentMedia.value = sortedMap.values.toList()
                     }
-                } else if (sortedMap.size > sortedMedia.size) {
+                } else if (sortedMap.size > sortedMedia.size && it.active) {
                     _currentMedia.value = sortedMap.values.toList()
-                } else if (sortedMap.size == sortedMedia.size) {
+                } else {
                     // When loading an update for an existing media control.
                     val currentList =
                         mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) }
@@ -296,6 +296,18 @@
         mediaFromRecPackageName = packageName
     }
 
+    fun hasActiveMedia(): Boolean {
+        return _selectedUserEntries.value.any { it.value.active }
+    }
+
+    fun hasAnyMedia(): Boolean {
+        return _selectedUserEntries.value.entries.isNotEmpty()
+    }
+
+    fun isRecommendationActive(): Boolean {
+        return _smartspaceMediaData.value.isActive
+    }
+
     private fun canBeRemoved(data: MediaData): Boolean {
         return data.isPlaying?.let { !it } ?: data.isClearable && !data.active
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index aa93df7..8b2f619 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -358,7 +358,12 @@
                             // LocalMediaManager. Override with routing session name if available to
                             // show dynamic group name.
                             connectedDevice?.copy(name = it.name ?: connectedDevice.name)
-                        }
+                        } ?: MediaDeviceData(
+                            enabled = false,
+                            icon = context.getDrawable(R.drawable.ic_media_home_devices),
+                            name = context.getString(R.string.media_seamless_other_device),
+                            showBroadcastButton = false
+                        )
                 } else {
                     // Prefer SASS if available when playback is local.
                     activeDevice = getSassDevice() ?: connectedDevice
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index b4bd4fd..0630cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.controls.data.repository.MediaDataRepository
 import com.android.systemui.media.controls.data.repository.MediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.MediaDataCombineLatest
 import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
@@ -45,7 +44,6 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business logic for media pipeline. */
@@ -55,7 +53,6 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    private val mediaDataRepository: MediaDataRepository,
     private val mediaDataProcessor: MediaDataProcessor,
     private val mediaTimeoutListener: MediaTimeoutListener,
     private val mediaResumeListener: MediaResumeListener,
@@ -103,26 +100,6 @@
                 initialValue = false,
             )
 
-    /** Are there any media notifications active, excluding the recommendations? */
-    val hasActiveMedia: StateFlow<Boolean> =
-        mediaFilterRepository.selectedUserEntries
-            .mapLatest { entries -> entries.any { it.value.active } }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Are there any media notifications, excluding the recommendations? */
-    val hasAnyMedia: StateFlow<Boolean> =
-        mediaFilterRepository.selectedUserEntries
-            .mapLatest { entries -> entries.isNotEmpty() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
     /** The current list for user media instances */
     val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
 
@@ -235,11 +212,11 @@
 
     override fun hasAnyMediaOrRecommendation() = hasAnyMediaOrRecommendation.value
 
-    override fun hasActiveMedia() = hasActiveMedia.value
+    override fun hasActiveMedia() = mediaFilterRepository.hasActiveMedia()
 
-    override fun hasAnyMedia() = hasAnyMedia.value
+    override fun hasAnyMedia() = mediaFilterRepository.hasAnyMedia()
 
-    override fun isRecommendationActive() = mediaDataRepository.smartspaceMediaData.value.isActive
+    override fun isRecommendationActive() = mediaFilterRepository.isRecommendationActive()
 
     fun reorderMedia() {
         mediaFilterRepository.setOrderedMedia()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 8f15561..987b370 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -216,7 +216,7 @@
     private var carouselLocale: Locale? = null
 
     private val animationScaleObserver: ContentObserver =
-        object : ContentObserver(null) {
+        object : ContentObserver(executor, 0) {
             override fun onChange(selfChange: Boolean) {
                 if (!mediaFlags.isSceneContainerEnabled()) {
                     MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
@@ -396,10 +396,12 @@
         }
 
         // Notifies all active players about animation scale changes.
-        globalSettings.registerContentObserverSync(
-            Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
-            animationScaleObserver
-        )
+        bgExecutor.execute {
+            globalSettings.registerContentObserverSync(
+                    Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+                    animationScaleObserver
+            )
+        }
     }
 
     private fun setUpListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index 4e90936..315a9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -201,6 +201,7 @@
     ) {
         if (immediatelyRemove || isReorderingAllowed()) {
             interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
+            mediaRecs = null
             if (!immediatelyRemove) {
                 // Although it wasn't requested, we were able to process the removal
                 // immediately since reordering is allowed. So, notify hosts to update
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index e1b21ef..9233e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
-import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -40,6 +39,13 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.addOutline
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.Dp
@@ -268,10 +274,9 @@
     private fun CurrentTilesContainer(content: @Composable () -> Unit) {
         Box(
             Modifier.fillMaxWidth()
-                .border(
-                    width = 1.dp,
-                    color = MaterialTheme.colorScheme.onBackground,
-                    shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
+                .dashedBorder(
+                    color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
+                    shape = Dimensions.ContainerShape,
                 )
                 .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
         ) {
@@ -286,7 +291,7 @@
                 .background(
                     color = MaterialTheme.colorScheme.background,
                     alpha = { 1f },
-                    shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
+                    shape = Dimensions.ContainerShape,
                 )
                 .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
         ) {
@@ -305,4 +310,27 @@
             item(span = { GridItemSpan(maxCurrentLineSpan) }) { Spacer(Modifier) }
         }
     }
+
+    private fun Modifier.dashedBorder(
+        color: Color,
+        shape: Shape,
+    ): Modifier {
+        return this.drawWithContent {
+            val outline = shape.createOutline(size, layoutDirection, this)
+            val path = Path()
+            path.addOutline(outline)
+            val stroke =
+                Stroke(
+                    width = 1.dp.toPx(),
+                    pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
+                )
+            this.drawContent()
+            drawPath(path = path, style = stroke, color = color)
+        }
+    }
+
+    private object Dimensions {
+        // Corner radius is half the height of a tile + padding
+        val ContainerShape = RoundedCornerShape(48.dp)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index e4fbb4b..bbb98d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalFoundationApi::class)
+
 package com.android.systemui.qs.panels.ui.compose
 
 import android.graphics.drawable.Animatable
@@ -27,17 +29,17 @@
 import androidx.compose.animation.graphics.vector.AnimatedImageVector
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
 import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -46,11 +48,6 @@
 import androidx.compose.foundation.lazy.grid.LazyGridScope
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Remove
-import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -68,7 +65,6 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
@@ -78,9 +74,11 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.load
+import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
@@ -90,13 +88,14 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.res.R
+import java.util.function.Supplier
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.mapLatest
 
 object TileType
 
-@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 @Composable
 fun Tile(
     tile: TileViewModel,
@@ -110,46 +109,143 @@
             .collectAsStateWithLifecycle(tile.currentState.toUiState())
     val colors = TileDefaults.getColorForState(state.state)
 
-    val context = LocalContext.current
-
-    Expandable(
-        color = colors.background,
-        shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
+    TileContainer(
+        colors = colors,
+        showLabels = showLabels,
+        label = state.label.toString(),
+        iconOnly = iconOnly,
+        onClick = tile::onClick,
+        onLongClick = tile::onLongClick,
+        modifier = modifier,
     ) {
-        Row(
-            modifier =
-                modifier
-                    .combinedClickable(
-                        onClick = { tile.onClick(it) },
-                        onLongClick = { tile.onLongClick(it) }
-                    )
-                    .tileModifier(colors),
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement = tileHorizontalArrangement(iconOnly),
-        ) {
-            val icon =
-                remember(state.icon) {
-                    state.icon.get().let {
-                        if (it is QSTileImpl.ResourceIcon) {
-                            Icon.Resource(it.resId, null)
-                        } else {
-                            Icon.Loaded(it.getDrawable(context), null)
-                        }
-                    }
-                }
-            TileContent(
+        val icon = getTileIcon(icon = state.icon)
+        if (iconOnly) {
+            TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
+        } else {
+            LargeTileContent(
                 label = state.label.toString(),
                 secondaryLabel = state.secondaryLabel.toString(),
                 icon = icon,
                 colors = colors,
-                iconOnly = iconOnly,
-                showLabels = showLabels,
+                onClick = tile::onSecondaryClick,
+                onLongClick = tile::onLongClick,
             )
         }
     }
 }
 
 @Composable
+private fun TileContainer(
+    colors: TileColors,
+    showLabels: Boolean,
+    label: String,
+    iconOnly: Boolean,
+    clickEnabled: Boolean = true,
+    onClick: (Expandable) -> Unit = {},
+    onLongClick: (Expandable) -> Unit = {},
+    modifier: Modifier = Modifier,
+    content: @Composable BoxScope.() -> Unit,
+) {
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement =
+            spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin), Alignment.Top),
+        modifier = modifier,
+    ) {
+        val backgroundColor =
+            if (iconOnly) {
+                colors.iconBackground
+            } else {
+                colors.background
+            }
+        Expandable(
+            color = backgroundColor,
+            shape = TileDefaults.TileShape,
+            modifier =
+                Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                    .clip(TileDefaults.TileShape)
+        ) {
+            Box(
+                modifier =
+                    Modifier.fillMaxSize()
+                        .combinedClickable(
+                            enabled = clickEnabled,
+                            onClick = { onClick(it) },
+                            onLongClick = { onLongClick(it) }
+                        )
+                        .tilePadding(),
+            ) {
+                content()
+            }
+        }
+
+        if (showLabels && iconOnly) {
+            Text(
+                label,
+                maxLines = 2,
+                color = colors.label,
+                overflow = TextOverflow.Ellipsis,
+                textAlign = TextAlign.Center,
+            )
+        }
+    }
+}
+
+@Composable
+private fun LargeTileContent(
+    label: String,
+    secondaryLabel: String?,
+    icon: Icon,
+    colors: TileColors,
+    clickEnabled: Boolean = true,
+    onClick: (Expandable) -> Unit = {},
+    onLongClick: (Expandable) -> Unit = {},
+) {
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        horizontalArrangement = tileHorizontalArrangement()
+    ) {
+        Expandable(
+            color = colors.iconBackground,
+            shape = TileDefaults.TileShape,
+            modifier = Modifier.fillMaxHeight().aspectRatio(1f)
+        ) {
+            Box(
+                modifier =
+                    Modifier.fillMaxSize()
+                        .clip(TileDefaults.TileShape)
+                        .combinedClickable(
+                            enabled = clickEnabled,
+                            onClick = { onClick(it) },
+                            onLongClick = { onLongClick(it) }
+                        )
+            ) {
+                TileIcon(
+                    icon = icon,
+                    color = colors.icon,
+                    modifier = Modifier.align(Alignment.Center)
+                )
+            }
+        }
+
+        Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+            Text(
+                label,
+                color = colors.label,
+                modifier = Modifier.basicMarquee(),
+            )
+            if (!TextUtils.isEmpty(secondaryLabel)) {
+                Text(
+                    secondaryLabel ?: "",
+                    color = colors.secondaryLabel,
+                    modifier = Modifier.basicMarquee(),
+                )
+            }
+        }
+    }
+}
+
+@Composable
 fun TileLazyGrid(
     modifier: Modifier = Modifier,
     columns: GridCells,
@@ -247,41 +343,19 @@
                 ""
             }
 
-        Box(
+        val iconOnly = isIconOnly(viewModel.tileSpec)
+        val tileHeight = tileHeight(iconOnly && showLabels)
+        EditTile(
+            tileViewModel = viewModel,
+            iconOnly = iconOnly,
+            showLabels = showLabels,
+            clickEnabled = canClick,
+            onClick = { onClick.invoke(viewModel.tileSpec) },
             modifier =
-                Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
-                    .animateItem()
-                    .semantics {
-                        onClick(onClickActionName) { false }
-                        this.stateDescription = stateDescription
-                    }
-        ) {
-            val iconOnly = isIconOnly(viewModel.tileSpec)
-            val tileHeight = tileHeight(iconOnly && showLabels)
-            EditTile(
-                tileViewModel = viewModel,
-                iconOnly = iconOnly,
-                showLabels = showLabels,
-                modifier = Modifier.height(tileHeight)
-            )
-            if (canClick) {
-                Badge(clickAction, Modifier.align(Alignment.TopEnd))
-            }
-        }
-    }
-}
-
-@Composable
-fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
-    Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
-        Icon(
-            imageVector =
-                when (action) {
-                    ClickAction.ADD -> Icons.Filled.Add
-                    ClickAction.REMOVE -> Icons.Filled.Remove
-                },
-            "",
-            tint = Color.Black,
+                Modifier.height(tileHeight).animateItem().semantics {
+                    onClick(onClickActionName) { false }
+                    this.stateDescription = stateDescription
+                }
         )
     }
 }
@@ -291,25 +365,40 @@
     tileViewModel: EditTileViewModel,
     iconOnly: Boolean,
     showLabels: Boolean,
+    clickEnabled: Boolean,
+    onClick: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
     val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
     val colors = TileDefaults.inactiveTileColors()
 
-    Row(
-        modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
-        verticalAlignment = Alignment.CenterVertically,
-        horizontalArrangement = tileHorizontalArrangement(iconOnly)
+    TileContainer(
+        colors = colors,
+        showLabels = showLabels,
+        label = label,
+        iconOnly = iconOnly,
+        clickEnabled = clickEnabled,
+        onClick = { onClick() },
+        onLongClick = { onClick() },
+        modifier = modifier,
     ) {
-        TileContent(
-            label = label,
-            secondaryLabel = tileViewModel.appName?.load(),
-            colors = colors,
-            icon = tileViewModel.icon,
-            iconOnly = iconOnly,
-            showLabels = showLabels,
-            animateIconToEnd = true,
-        )
+        if (iconOnly) {
+            TileIcon(
+                icon = tileViewModel.icon,
+                color = colors.icon,
+                modifier = Modifier.align(Alignment.Center)
+            )
+        } else {
+            LargeTileContent(
+                label = label,
+                secondaryLabel = tileViewModel.appName?.load(),
+                icon = tileViewModel.icon,
+                colors = colors,
+                clickEnabled = clickEnabled,
+                onClick = { onClick() },
+                onLongClick = { onClick() },
+            )
+        }
     }
 }
 
@@ -318,14 +407,27 @@
     REMOVE,
 }
 
+@Composable
+private fun getTileIcon(icon: Supplier<QSTile.Icon>): Icon {
+    val context = LocalContext.current
+    return icon.get().let {
+        if (it is QSTileImpl.ResourceIcon) {
+            Icon.Resource(it.resId, null)
+        } else {
+            Icon.Loaded(it.getDrawable(context), null)
+        }
+    }
+}
+
 @OptIn(ExperimentalAnimationGraphicsApi::class)
 @Composable
 private fun TileIcon(
     icon: Icon,
     color: Color,
     animateToEnd: Boolean = false,
+    modifier: Modifier = Modifier,
 ) {
-    val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+    val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
     val context = LocalContext.current
     val loadedDrawable =
         remember(icon, context) {
@@ -338,7 +440,7 @@
         Icon(
             icon = icon,
             tint = color,
-            modifier = modifier,
+            modifier = iconModifier,
         )
     } else if (icon is Icon.Resource) {
         val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
@@ -357,80 +459,25 @@
             painter = painter,
             contentDescription = null,
             colorFilter = ColorFilter.tint(color = color),
-            modifier = modifier
+            modifier = iconModifier
         )
     }
 }
 
 @Composable
-private fun Modifier.tileModifier(colors: TileColors): Modifier {
-    return fillMaxWidth()
-        .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
-        .background(colors.background)
-        .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
+private fun Modifier.tilePadding(): Modifier {
+    return padding(dimensionResource(id = R.dimen.qs_label_container_margin))
 }
 
 @Composable
-private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
-    val horizontalAlignment =
-        if (iconOnly) {
-            Alignment.CenterHorizontally
-        } else {
-            Alignment.Start
-        }
+private fun tileHorizontalArrangement(): Arrangement.Horizontal {
     return spacedBy(
         space = dimensionResource(id = R.dimen.qs_label_container_margin),
-        alignment = horizontalAlignment
+        alignment = Alignment.Start
     )
 }
 
 @Composable
-private fun TileContent(
-    label: String,
-    secondaryLabel: String?,
-    icon: Icon,
-    colors: TileColors,
-    iconOnly: Boolean,
-    showLabels: Boolean = false,
-    animateIconToEnd: Boolean = false,
-) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.Center,
-        modifier = Modifier.fillMaxHeight()
-    ) {
-        TileIcon(icon, colors.icon, animateIconToEnd)
-
-        if (iconOnly && showLabels) {
-            Text(
-                label,
-                maxLines = 2,
-                color = colors.label,
-                overflow = TextOverflow.Ellipsis,
-                textAlign = TextAlign.Center,
-            )
-        }
-    }
-
-    if (!iconOnly) {
-        Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
-            Text(
-                label,
-                color = colors.label,
-                modifier = Modifier.basicMarquee(),
-            )
-            if (!TextUtils.isEmpty(secondaryLabel)) {
-                Text(
-                    secondaryLabel ?: "",
-                    color = colors.secondaryLabel,
-                    modifier = Modifier.basicMarquee(),
-                )
-            }
-        }
-    }
-}
-
-@Composable
 fun tileHeight(iconWithLabel: Boolean = false): Dp {
     return if (iconWithLabel) {
         TileDefaults.IconTileWithLabelHeight
@@ -441,20 +488,23 @@
 
 private data class TileColors(
     val background: Color,
+    val iconBackground: Color,
     val label: Color,
     val secondaryLabel: Color,
     val icon: Color,
 )
 
 private object TileDefaults {
-    val IconTileWithLabelHeight = 100.dp
+    val TileShape = CircleShape
+    val IconTileWithLabelHeight = 140.dp
 
     @Composable
     fun activeTileColors(): TileColors =
         TileColors(
-            background = MaterialTheme.colorScheme.primary,
-            label = MaterialTheme.colorScheme.onPrimary,
-            secondaryLabel = MaterialTheme.colorScheme.onPrimary,
+            background = MaterialTheme.colorScheme.surfaceVariant,
+            iconBackground = MaterialTheme.colorScheme.primary,
+            label = MaterialTheme.colorScheme.onSurfaceVariant,
+            secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
             icon = MaterialTheme.colorScheme.onPrimary,
         )
 
@@ -462,6 +512,7 @@
     fun inactiveTileColors(): TileColors =
         TileColors(
             background = MaterialTheme.colorScheme.surfaceVariant,
+            iconBackground = MaterialTheme.colorScheme.surfaceVariant,
             label = MaterialTheme.colorScheme.onSurfaceVariant,
             secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
             icon = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -471,6 +522,7 @@
     fun unavailableTileColors(): TileColors =
         TileColors(
             background = MaterialTheme.colorScheme.surface,
+            iconBackground = MaterialTheme.colorScheme.surface,
             label = MaterialTheme.colorScheme.onSurface,
             secondaryLabel = MaterialTheme.colorScheme.onSurface,
             icon = MaterialTheme.colorScheme.onSurface,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index a6cfa75..7505b90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -48,6 +48,10 @@
         tile.longClick(expandable)
     }
 
+    fun onSecondaryClick(expandable: Expandable?) {
+        tile.secondaryClick(expandable)
+    }
+
     fun startListening(token: Any) = tile.setListening(token, true)
 
     fun stopListening(token: Any) = tile.setListening(token, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 7c92ede..42ab25f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.testKosmos
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,6 +79,9 @@
     fun setup() {
         underTest.start()
 
+        kosmos.fakeKeyguardRepository.setDreaming(true)
+        kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(true)
+
         // Transition to DOZING and set the power interactor asleep.
         powerInteractor.setAsleepForTest()
         runBlocking {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
index 88fe4ce..af76b08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -16,12 +16,18 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -81,6 +87,7 @@
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionsToLockscreen_ifFinishedInGone() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionSteps(
@@ -92,7 +99,34 @@
             kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
-            // We're in the middle of a LOCKSCREEN -> GONE transition.
+            // We're in the middle of a GONE -> LOCKSCREEN transition.
+            assertThat(keyguardTransitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionsToLockscreen_ifFinishedInGone_wmRefactor() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            reset(keyguardTransitionRepository)
+
+            // Trigger lockdown.
+            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(
+                    0,
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+                )
+            )
+            runCurrent()
+
+            // We're in the middle of a GONE -> LOCKSCREEN transition.
             assertThat(keyguardTransitionRepository)
                 .startedTransition(
                     to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 8471fe1..064cf09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -663,6 +663,7 @@
                     true
                 )
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
 
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
@@ -706,6 +707,7 @@
                     true
                 )
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
@@ -760,6 +762,7 @@
                 )
 
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
 
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
@@ -834,6 +837,7 @@
                 )
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
 
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
@@ -922,6 +926,7 @@
             // If there is media that was recently played but inactive
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
@@ -986,6 +991,7 @@
             // WHEN we have media that was recently played, but not currently active
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
@@ -1039,6 +1045,7 @@
             // WHEN we have media that was recently played, but not currently active
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+            repository.setOrderedMedia()
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 18b4c48..3b541cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -242,7 +242,6 @@
         mediaCarouselInteractor =
             MediaCarouselInteractor(
                 applicationScope = testScope.backgroundScope,
-                mediaDataRepository = mediaDataRepository,
                 mediaDataProcessor = mediaDataProcessor,
                 mediaTimeoutListener = mediaTimeoutListener,
                 mediaResumeListener = mediaResumeListener,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 42bd46f..5142730 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.TestStubDrawable
 import android.media.MediaRoute2Info
 import android.media.MediaRouter2Manager
 import android.media.RoutingSessionInfo
@@ -30,6 +31,7 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -89,6 +91,11 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 public class MediaDeviceManagerTest : SysuiTestCase() {
+
+    private companion object {
+        val OTHER_DEVICE_ICON_STUB = TestStubDrawable()
+    }
+
     @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
     private lateinit var manager: MediaDeviceManager
@@ -155,6 +162,11 @@
             MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
         whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
         setupLeAudioConfiguration(false)
+
+        context.orCreateTestableResources.addOverride(
+            R.drawable.ic_media_home_devices,
+            OTHER_DEVICE_ICON_STUB
+        )
     }
 
     @After
@@ -414,6 +426,7 @@
         assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
     }
 
+    @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
     fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
         // GIVEN that MR2Manager returns null for routing session
@@ -429,6 +442,24 @@
         assertThat(data.name).isNull()
     }
 
+    @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+    @Test
+    fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() {
+        // GIVEN that MR2Manager returns null for routing session
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is disabled and name and icon are set to "OTHER DEVICE".
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device))
+        assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB)
+    }
+
+    @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
     fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
         // GIVEN a notif is added
@@ -449,7 +480,30 @@
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isNull()
     }
+    @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+    @Test
+    fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_returnOtherDevice() {
+        // GIVEN a notif is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+        // AND MR2Manager returns null for routing session
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        // WHEN the selected device changes state
+        val deviceCallback = captureCallback()
+        deviceCallback.onSelectedDeviceStateChanged(device, 1)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is disabled and name and icon are set to "OTHER DEVICE".
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device))
+        assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB)
+    }
 
+    @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
     fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
         // GIVEN a notif is added
@@ -471,6 +525,29 @@
         assertThat(data.name).isNull()
     }
 
+    @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+    @Test
+    fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() {
+        // GIVEN a notif is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+        // GIVEN that MR2Manager returns null for routing session
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        // WHEN the selected device changes state
+        val deviceCallback = captureCallback()
+        deviceCallback.onDeviceListUpdate(mutableListOf(device))
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN device is disabled and name and icon are set to "OTHER DEVICE".
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device))
+        assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB)
+    }
+
     // With the flag enabled, MediaDeviceManager no longer gathers device name information directly.
     @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 6a2637d..ccf926a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -193,11 +193,12 @@
         whenever(panel.mediaViewController).thenReturn(mediaViewController)
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         MediaPlayerData.clear()
+        FakeExecutor.exhaustExecutors(bgExecutor)
         verify(globalSettings)
-            .registerContentObserverSync(
-                eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
-                capture(settingsObserverCaptor)
-            )
+                .registerContentObserverSync(
+                        eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
+                        capture(settingsObserverCaptor)
+                )
     }
 
     @After
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 162fd90..28bd439 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -34,7 +33,6 @@
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
-            flags = featureFlagsClassic,
             shadeRepository = shadeRepository,
             powerInteractor = powerInteractor,
             glanceableHubTransitions = glanceableHubTransitions,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 98babff..d72b9c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -18,7 +18,6 @@
 
 import com.android.keyguard.keyguardSecurityModel
 import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -37,7 +36,6 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             communalInteractor = communalInteractor,
-            flags = featureFlagsClassic,
             keyguardSecurityModel = keyguardSecurityModel,
             selectedUserInteractor = selectedUserInteractor,
             powerInteractor = powerInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractorKosmos.kt
index e5e2aff..ca1b3f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractorKosmos.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.media.controls.data.repository.mediaDataRepository
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.mediaDataCombineLatest
 import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
@@ -33,7 +32,6 @@
     Kosmos.Fixture {
         MediaCarouselInteractor(
             applicationScope = applicationCoroutineScope,
-            mediaDataRepository = mediaDataRepository,
             mediaDataProcessor = mediaDataProcessor,
             mediaTimeoutListener = mediaTimeoutListener,
             mediaResumeListener = mediaResumeListener,
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 78edb8e..1831ecd 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -689,12 +689,20 @@
                 Slog.v(TAG, "AutofillWindowPresenter.show(): fit=" + fitsSystemWindows
                         + ", params=" + paramsToString(p));
             }
-            UiThread.getHandler().post(() -> mWindow.show(p));
+            UiThread.getHandler().post(() -> {
+                if (mWindow != null) {
+                    mWindow.show(p);
+                }
+            });
         }
 
         @Override
         public void hide(Rect transitionEpicenter) {
-            UiThread.getHandler().post(mWindow::hide);
+            UiThread.getHandler().post(() -> {
+                if (mWindow != null) {
+                    mWindow.hide();
+                }
+            });
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 209cbb7..e34bdc6 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -728,7 +728,14 @@
         final int compilationReason =
                 dexManager.getCompilationReasonForInstallScenario(
                         installRequest.getInstallScenario());
-        return new DexoptOptions(packageName, compilationReason, dexoptFlags);
+        final AndroidPackage pkg = ps.getPkg();
+        var options = new DexoptOptions(packageName, compilationReason, dexoptFlags);
+        if (installRequest.getDexoptCompilerFilter() != null) {
+            options = options.overrideCompilerFilter(installRequest.getDexoptCompilerFilter());
+        } else if (pkg != null && pkg.isDebuggable()) {
+            options = options.overrideCompilerFilter(DexoptParams.COMPILER_FILTER_NOOP);
+        }
+        return options;
     }
 
     /**
@@ -772,12 +779,12 @@
                 && installRequest.getInstallSource().mInitiatingPackageName.equals("android"))
                 : true;
 
+        // Don't skip the dexopt call if the compiler filter is "skip". Instead, call dexopt with
+        // the "skip" filter so that ART Service gets notified and skips dexopt itself.
         return (!instantApp || Global.getInt(context.getContentResolver(),
                 Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
                 && pkg != null
-                && !pkg.isDebuggable()
                 && (!onIncremental)
-                && dexoptOptions.isCompilationEnabled()
                 && !isApex
                 && performDexOptForRollback;
     }
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index 46f9732..8001615 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -58,6 +58,8 @@
     final int mDataLoaderType;
     final int mPackageSource;
     final boolean mApplicationEnabledSettingPersistent;
+    @Nullable
+    final String mDexoptCompilerFilter;
 
     // The list of instruction sets supported by this app. This is currently
     // only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -73,7 +75,7 @@
             int autoRevokePermissionsMode, String traceMethod, int traceCookie,
             SigningDetails signingDetails, int installReason, int installScenario,
             boolean forceQueryableOverride, int dataLoaderType, int packageSource,
-            boolean applicationEnabledSettingPersistent) {
+            boolean applicationEnabledSettingPersistent, String dexoptCompilerFilter) {
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mInstallFlags = installFlags;
@@ -96,5 +98,6 @@
         mDataLoaderType = dataLoaderType;
         mPackageSource = packageSource;
         mApplicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
+        mDexoptCompilerFilter = dexoptCompilerFilter;
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 8f51e36..dd2583a0d 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -174,13 +174,13 @@
         mUserId = params.getUser().getIdentifier();
         mInstallArgs = new InstallArgs(params.mOriginInfo, params.mMoveInfo, params.mObserver,
                 params.mInstallFlags, params.mDevelopmentInstallFlags, params.mInstallSource,
-                params.mVolumeUuid,  params.getUser(), null /*instructionSets*/,
+                params.mVolumeUuid, params.getUser(), null /*instructionSets*/,
                 params.mPackageAbiOverride, params.mPermissionStates,
                 params.mAllowlistedRestrictedPermissions, params.mAutoRevokePermissionsMode,
                 params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
                 params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
                 params.mDataLoaderType, params.mPackageSource,
-                params.mApplicationEnabledSettingPersistent);
+                params.mApplicationEnabledSettingPersistent, params.mDexoptCompilerFilter);
         mPackageLite = params.mPackageLite;
         mPackageMetrics = new PackageMetrics(this);
         mIsInstallInherit = params.mIsInherit;
@@ -709,6 +709,11 @@
         return mWarnings;
     }
 
+    @Nullable
+    public String getDexoptCompilerFilter() {
+        return mInstallArgs != null ? mInstallArgs.mDexoptCompilerFilter : null;
+    }
+
     public void setScanFlags(int scanFlags) {
         mScanFlags = scanFlags;
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index d3a18f9..ccc1175 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -102,6 +102,7 @@
     @Nullable
     final DomainSet mPreVerifiedDomains;
     final boolean mHasAppMetadataFile;
+    @Nullable final String mDexoptCompilerFilter;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -136,6 +137,7 @@
         mApplicationEnabledSettingPersistent = false;
         mPreVerifiedDomains = null;
         mHasAppMetadataFile = false;
+        mDexoptCompilerFilter = null;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
@@ -172,6 +174,7 @@
         mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
         mPreVerifiedDomains = preVerifiedDomains;
         mHasAppMetadataFile = hasAppMetadatafile;
+        mDexoptCompilerFilter = sessionParams.dexoptCompilerFilter;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index e2ddba5..819a75c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -18,7 +18,7 @@
 
 import android.os.SystemProperties;
 
-import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.art.model.DexoptParams;
 
 import dalvik.system.DexFile;
 
@@ -71,7 +71,7 @@
     private static String getAndCheckValidity(int reason) {
         String sysPropValue = SystemProperties.get(getSystemPropertyName(reason));
         if (sysPropValue == null || sysPropValue.isEmpty()
-                || !(sysPropValue.equals(DexoptOptions.COMPILER_FILTER_NOOP)
+                || !(sysPropValue.equals(DexoptParams.COMPILER_FILTER_NOOP)
                         || DexFile.isValidCompilerFilter(sysPropValue))) {
             throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
                     + "(reason " + REASON_STRINGS[reason] + ")");
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a876616..7a53fe7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -121,6 +121,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.model.DexoptParams;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.PermissionAllowlist;
@@ -3589,6 +3591,14 @@
                 case "--package-source":
                     sessionParams.setPackageSource(Integer.parseInt(getNextArg()));
                     break;
+                case "--dexopt-compiler-filter":
+                    sessionParams.dexoptCompilerFilter = getNextArgRequired();
+                    // An early check that throws IllegalArgumentException if the compiler filter is
+                    // invalid.
+                    new DexoptParams.Builder(ReasonMapping.REASON_INSTALL)
+                            .setCompilerFilter(sessionParams.dexoptCompilerFilter)
+                            .build();
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -4735,6 +4745,7 @@
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
         pw.println("       [--apex] [--non-staged] [--force-non-staged]");
         pw.println("       [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]");
+        pw.println("       [--dexopt-compiler-filter FILTER]");
         pw.println("       [PATH [SPLIT...]|-]");
         pw.println("    Install an application.  Must provide the apk data to install, either as");
         pw.println("    file path(s) or '-' to read from stdin.  Options are:");
@@ -4781,13 +4792,19 @@
         pw.println("          milliseconds for pre-reboot verification to complete when");
         pw.println("          performing staged install. This flag is used to alter the waiting");
         pw.println("          time. You can skip the waiting time by specifying a TIMEOUT of '0'");
-        pw.println("      --ignore-dexopt-profile: If set, all profiles are ignored by dexopt");
+        pw.println("      --ignore-dexopt-profile: if set, all profiles are ignored by dexopt");
         pw.println("          during the installation, including the profile in the DM file and");
         pw.println("          the profile embedded in the APK file. If an invalid profile is");
         pw.println("          provided during installation, no warning will be reported by `adb");
         pw.println("          install`.");
         pw.println("          This option does not affect later dexopt operations (e.g.,");
         pw.println("          background dexopt and manual `pm compile` invocations).");
+        pw.println("      --dexopt-compiler-filter: the target compiler filter for dexopt during");
+        pw.println("          the installation. The filter actually used may be different.");
+        pw.println("          Valid values: one of the values documented in");
+        pw.println("          https://source.android.com/docs/core/runtime/configure"
+                + "#compiler_filters");
+        pw.println("          or 'skip'");
         pw.println("");
         pw.println("  install-existing [--user USER_ID|all|current]");
         pw.println("       [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index fcdc008..8cf248d 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -73,12 +73,6 @@
     // or device setup and should be scheduled appropriately.
     public static final int DEXOPT_FOR_RESTORE = 1 << 11; // TODO(b/135202722): remove
 
-    /**
-     * A value indicating that dexopt shouldn't be run.  This string is only used when loading
-     * filters from the `pm.dexopt.install*` properties and is not propagated to dex2oat.
-     */
-    public static final String COMPILER_FILTER_NOOP = "skip";
-
     // The name of package to optimize.
     private final String mPackageName;
 
@@ -186,10 +180,6 @@
         return mCompilationReason;
     }
 
-    public boolean isCompilationEnabled() {
-        return !mCompilerFilter.equals(COMPILER_FILTER_NOOP);
-    }
-
     /**
      * Creates a new set of DexoptOptions which are the same with the exception of the compiler
      * filter (set to the given value).
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d7a696f..e6d8132 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -139,6 +139,7 @@
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
+import com.android.wm.shell.Flags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -1723,7 +1724,14 @@
         // Get top task at beginning because the order may be changed when reusing existing task.
         final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
         final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;
-        final Task reusedTask = resolveReusableTask();
+        final boolean sourceActivityLaunchedFromBubble =
+                sourceRecord != null && sourceRecord.getLaunchedFromBubble();
+        // if the flag is enabled, allow reusing bubbled tasks only if the source activity is
+        // bubbled.
+        final boolean includeLaunchedFromBubble =
+                Flags.onlyReuseBubbledTaskWhenLaunchedFromBubble()
+                        ? sourceActivityLaunchedFromBubble : true;
+        final Task reusedTask = resolveReusableTask(includeLaunchedFromBubble);
 
         // If requested, freeze the task list
         if (mOptions != null && mOptions.freezeRecentTasksReordering()
@@ -2722,8 +2730,11 @@
     /**
      * Decide whether the new activity should be inserted into an existing task. Returns null
      * if not or an ActivityRecord with the task into which the new activity should be added.
+     *
+     * @param includeLaunchedFromBubble whether a task whose top activity was launched from a bubble
+     *                                  should be allowed to be reused for the new activity.
      */
-    private Task resolveReusableTask() {
+    private Task resolveReusableTask(boolean includeLaunchedFromBubble) {
         // If a target task is specified, try to reuse that one
         if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) {
             Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId());
@@ -2767,7 +2778,8 @@
             } else {
                 // Otherwise find the best task to put the activity in.
                 intentActivity =
-                        mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea);
+                        mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea,
+                                includeLaunchedFromBubble);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4770358..54ba47e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -314,13 +314,19 @@
         private boolean isDocument;
         private Uri documentData;
 
-        void init(int activityType, String taskAffinity, Intent intent, ActivityInfo info) {
+        // determines whether to include bubbled tasks. defaults to true to preserve previous
+        // behavior.
+        private boolean mIncludeLaunchedFromBubble = true;
+
+        void init(int activityType, String taskAffinity, Intent intent, ActivityInfo info,
+                boolean includeLaunchedFromBubble) {
             mActivityType = activityType;
             mTaskAffinity = taskAffinity;
             mIntent = intent;
             mInfo = info;
             mIdealRecord = null;
             mCandidateRecord = null;
+            mIncludeLaunchedFromBubble = includeLaunchedFromBubble;
         }
 
         /**
@@ -362,7 +368,8 @@
             }
 
             // Overlays should not be considered as the task's logical top activity.
-            final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */);
+            final ActivityRecord r = task.getTopNonFinishingActivity(
+                    false /* includeOverlays */, mIncludeLaunchedFromBubble);
 
             if (r == null || r.finishing || r.mUserId != userId
                     || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
@@ -2372,18 +2379,20 @@
     }
 
     @Nullable
-    ActivityRecord findTask(ActivityRecord r, TaskDisplayArea preferredTaskDisplayArea) {
+    ActivityRecord findTask(ActivityRecord r, TaskDisplayArea preferredTaskDisplayArea,
+            boolean includeLaunchedFromBubble) {
         return findTask(r.getActivityType(), r.taskAffinity, r.intent, r.info,
-                preferredTaskDisplayArea);
+                preferredTaskDisplayArea, includeLaunchedFromBubble);
     }
 
     @Nullable
     ActivityRecord findTask(int activityType, String taskAffinity, Intent intent, ActivityInfo info,
-            TaskDisplayArea preferredTaskDisplayArea) {
+            TaskDisplayArea preferredTaskDisplayArea, boolean includeLaunchedFromBubble) {
         ProtoLog.d(WM_DEBUG_TASKS, "Looking for task of type=%s, taskAffinity=%s, intent=%s"
-                        + ", info=%s, preferredTDA=%s", activityType, taskAffinity, intent, info,
-                preferredTaskDisplayArea);
-        mTmpFindTaskResult.init(activityType, taskAffinity, intent, info);
+                        + ", info=%s, preferredTDA=%s, includeLaunchedFromBubble=%b", activityType,
+                taskAffinity, intent, info, preferredTaskDisplayArea, includeLaunchedFromBubble);
+        mTmpFindTaskResult.init(activityType, taskAffinity, intent, info,
+                includeLaunchedFromBubble);
 
         // Looking up task on preferred display area first
         ActivityRecord candidateActivity = null;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 187b105..ab72e3c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1104,21 +1104,34 @@
     }
 
     ActivityRecord getTopNonFinishingActivity() {
-        return getTopNonFinishingActivity(true /* includeOverlays */);
+        return getTopNonFinishingActivity(
+                true /* includeOverlays */, true /* includeLaunchedFromBubble */);
     }
 
     /**
      * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
      * the current user.
      * @param includeOverlays whether the task overlay activity should be included.
+     * @param includeLaunchedFromBubble whether activities that were launched from a bubble should
+     *                                  be included.
      * @see #topRunningActivity(boolean)
      */
-    ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
-        // Split into 2 to avoid object creation due to variable capture.
+    ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
+            boolean includeLaunchedFromBubble) {
+        // Split to avoid object creation due to variable capture.
         if (includeOverlays) {
-            return getActivity((r) -> !r.finishing);
+            if (includeLaunchedFromBubble) {
+                return getActivity(r -> !r.finishing);
+            } else {
+                return getActivity(r -> !r.finishing && !r.getLaunchedFromBubble());
+            }
         }
-        return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
+        if (includeLaunchedFromBubble) {
+            return getActivity(r -> !r.finishing && !r.isTaskOverlay());
+        } else {
+            return getActivity(
+                    r -> !r.finishing && !r.isTaskOverlay() && !r.getLaunchedFromBubble());
+        }
     }
 
     ActivityRecord topRunningActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1fd8f50..ff1c6c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -95,6 +95,8 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.service.voice.IVoiceInteractionSession;
@@ -112,6 +114,7 @@
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
 import com.android.server.wm.utils.MockTracker;
+import com.android.wm.shell.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -492,7 +495,8 @@
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent);
-        doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+        doReturn(splitSecondReusableActivity)
+                .when(mRootWindowContainer).findTask(any(), any(), anyBoolean());
         final int result = starter.setReason("testSplitScreenDeliverToTop").execute();
 
         // Ensure result is delivering intent to top.
@@ -519,7 +523,8 @@
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent);
-        doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+        doReturn(splitSecondReusableActivity)
+                .when(mRootWindowContainer).findTask(any(), any(), anyBoolean());
         final int result = starter.setReason("testSplitScreenMoveToFront").execute();
 
         // Ensure result is moving task to front.
@@ -566,7 +571,7 @@
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(activities.get(3).mActivityComponent);
-        doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any());
+        doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any(), anyBoolean());
         final int result = starter.setReason("testDesktopModeDeliverToTop").execute();
 
         // Ensure result is delivering intent to top.
@@ -593,7 +598,8 @@
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent);
-        doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+        doReturn(desktopModeReusableActivity)
+                .when(mRootWindowContainer).findTask(any(), any(), anyBoolean());
         final int result = starter.setReason("testDesktopModeMoveToFront").execute();
 
         // Ensure result is moving task to front.
@@ -755,7 +761,7 @@
 
         final ActivityRecord baseActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         baseActivity.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED);
-        doReturn(baseActivity).when(mRootWindowContainer).findTask(any(), any());
+        doReturn(baseActivity).when(mRootWindowContainer).findTask(any(), any(), anyBoolean());
 
         ActivityOptions rawOptions = ActivityOptions.makeBasic()
                 .setPendingIntentCreatorBackgroundActivityStartMode(
@@ -1648,6 +1654,120 @@
         assertNotEquals(inTask, target.getTask());
     }
 
+    @EnableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE)
+    @Test
+    public void launchActivity_reusesBubbledTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord bubbledActivity = createBubbledActivity();
+
+        // create the target activity to be launched with the same component as the bubbled activity
+        final ActivityRecord targetRecord = new ActivityBuilder(mAtm)
+                .setLaunchMode(LAUNCH_SINGLE_TASK)
+                .setComponent(ActivityBuilder.getDefaultComponent()).build();
+        starter.getIntent().setComponent(bubbledActivity.mActivityComponent);
+        startActivityInner(starter, targetRecord, bubbledActivity, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertEquals(bubbledActivity.getTask(), targetRecord.getTask());
+    }
+
+    @EnableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE)
+    @Test
+    public void launchActivity_nullSourceRecord_doesNotReuseBubbledTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord bubbledActivity = createBubbledActivity();
+
+        // create the target activity to be launched
+        final ActivityRecord targetRecord =
+                new ActivityBuilder(mAtm)
+                        .setLaunchMode(LAUNCH_SINGLE_TASK)
+                        .setComponent(ActivityBuilder.getDefaultComponent()).build();
+        starter.getIntent().setComponent(bubbledActivity.mActivityComponent);
+
+        // pass null as the source record
+        startActivityInner(starter, targetRecord, null, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertNotEquals(bubbledActivity.getTask(), targetRecord.getTask());
+    }
+
+    @EnableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE)
+    @Test
+    public void launchActivity_nonBubbledSourceRecord_doesNotReuseBubbledTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord bubbledActivity = createBubbledActivity();
+
+        // create a non bubbled activity
+        final ActivityRecord nonBubbleSourceRecord =
+                new ActivityBuilder(mAtm).setCreateTask(true)
+                        .setLaunchMode(LAUNCH_SINGLE_TASK)
+                        .setComponent(ActivityBuilder.getDefaultComponent())
+                        .build();
+
+        // create the target activity to be launched
+        final ActivityRecord targetRecord =
+                new ActivityBuilder(mAtm)
+                        .setLaunchMode(LAUNCH_SINGLE_TASK)
+                        .setComponent(ActivityBuilder.getDefaultComponent()).build();
+        starter.getIntent().setComponent(bubbledActivity.mActivityComponent);
+
+        // use the non bubbled activity as the source
+        startActivityInner(starter, targetRecord, nonBubbleSourceRecord, null /* options */,
+                null /* inTask */, null /* inTaskFragment*/);
+
+        assertNotEquals(bubbledActivity.getTask(), targetRecord.getTask());
+    }
+
+    @DisableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE)
+    @Test
+    public void launchActivity_nullSourceRecord_flagDisabled_reusesBubbledTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord bubbledActivity = createBubbledActivity();
+
+        // create the target activity to be launched
+        final ActivityRecord targetRecord =
+                new ActivityBuilder(mAtm)
+                        .setLaunchMode(LAUNCH_SINGLE_TASK)
+                        .setComponent(ActivityBuilder.getDefaultComponent()).build();
+        starter.getIntent().setComponent(bubbledActivity.mActivityComponent);
+
+        // pass null as the source record
+        startActivityInner(starter, targetRecord, null, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertEquals(bubbledActivity.getTask(), targetRecord.getTask());
+    }
+
+    @DisableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE)
+    @Test
+    public void launchActivity_fromBubble_flagDisabled_reusesBubbledTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord bubbledActivity = createBubbledActivity();
+
+        // create the target activity to be launched with the same component as the bubbled activity
+        final ActivityRecord targetRecord =
+                new ActivityBuilder(mAtm)
+                        .setLaunchMode(LAUNCH_SINGLE_TASK)
+                        .setComponent(ActivityBuilder.getDefaultComponent()).build();
+        starter.getIntent().setComponent(bubbledActivity.mActivityComponent);
+        startActivityInner(starter, targetRecord, bubbledActivity, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertEquals(bubbledActivity.getTask(), targetRecord.getTask());
+    }
+
+    private ActivityRecord createBubbledActivity() {
+        final ActivityOptions opts = ActivityOptions.makeBasic();
+        opts.setTaskAlwaysOnTop(true);
+        opts.setLaunchedFromBubble(true);
+        opts.setLaunchBounds(new Rect(10, 10, 100, 100));
+        return new ActivityBuilder(mAtm)
+                .setCreateTask(true)
+                .setComponent(ActivityBuilder.getDefaultComponent())
+                .setActivityOptions(opts)
+                .build();
+    }
+
     private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
             ActivityRecord source, ActivityOptions options, Task inTask,
             TaskFragment inTaskFragment) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index ce90504..0f28528 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -405,11 +405,12 @@
 
         final RootWindowContainer.FindTaskResult result =
                 new RootWindowContainer.FindTaskResult();
-        result.init(r.getActivityType(), r.taskAffinity, r.intent, r.info);
+        result.init(r.getActivityType(), r.taskAffinity, r.intent, r.info, true);
         result.process(task);
 
-        assertEquals(r, task.getTopNonFinishingActivity(false /* includeOverlays */));
-        assertEquals(taskOverlay, task.getTopNonFinishingActivity(true /* includeOverlays */));
+        assertEquals(r, task.getTopNonFinishingActivity(false /* includeOverlays */, true));
+        assertEquals(
+                taskOverlay, task.getTopNonFinishingActivity(true /* includeOverlays */, true));
         assertNotNull(result.mIdealRecord);
     }
 
@@ -432,7 +433,7 @@
         final ActivityRecord r1 = new ActivityBuilder(mAtm).setComponent(
                 target).setTargetActivity(targetActivity).build();
         RootWindowContainer.FindTaskResult result = new RootWindowContainer.FindTaskResult();
-        result.init(r1.getActivityType(), r1.taskAffinity, r1.intent, r1.info);
+        result.init(r1.getActivityType(), r1.taskAffinity, r1.intent, r1.info, true);
         result.process(parentTask);
         assertThat(result.mIdealRecord).isNotNull();
 
@@ -440,7 +441,7 @@
         final ActivityRecord r2 = new ActivityBuilder(mAtm).setComponent(
                 alias).setTargetActivity(targetActivity).build();
         result = new RootWindowContainer.FindTaskResult();
-        result.init(r2.getActivityType(), r2.taskAffinity, r2.intent, r2.info);
+        result.init(r2.getActivityType(), r2.taskAffinity, r2.intent, r2.info, true);
         result.process(parentTask);
         assertThat(result.mIdealRecord).isNotNull();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index d88871c..eb79118 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -147,6 +147,40 @@
     }
 
     @Test
+    public void testFindTask_includeLaunchedFromBubbled() {
+        final ComponentName component = ComponentName.createRelative(
+                DEFAULT_COMPONENT_PACKAGE_NAME, ".BubbledActivity");
+        final ActivityOptions opts = ActivityOptions.makeBasic();
+        opts.setTaskAlwaysOnTop(true);
+        opts.setLaunchedFromBubble(true);
+        final ActivityRecord activity = new ActivityBuilder(mWm.mAtmService)
+                .setComponent(component)
+                .setActivityOptions(opts)
+                .setCreateTask(true)
+                .build();
+
+        assertEquals(activity, mWm.mRoot.findTask(activity, activity.getTaskDisplayArea(),
+                true /* includeLaunchedFromBubble */));
+    }
+
+    @Test
+    public void testFindTask_ignoreLaunchedFromBubbled() {
+        final ComponentName component = ComponentName.createRelative(
+                DEFAULT_COMPONENT_PACKAGE_NAME, ".BubbledActivity");
+        final ActivityOptions opts = ActivityOptions.makeBasic();
+        opts.setTaskAlwaysOnTop(true);
+        opts.setLaunchedFromBubble(true);
+        final ActivityRecord activity = new ActivityBuilder(mWm.mAtmService)
+                .setComponent(component)
+                .setActivityOptions(opts)
+                .setCreateTask(true)
+                .build();
+
+        assertNull(mWm.mRoot.findTask(activity, activity.getTaskDisplayArea(),
+                false /* includeLaunchedFromBubble */));
+    }
+
+    @Test
     public void testAllPausedActivitiesComplete() {
         DisplayContent displayContent = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
         ActivityRecord activity = createActivityRecord(displayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index d57a7e6..f94e5e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -56,6 +56,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 
+import android.app.ActivityOptions;
 import android.content.pm.SigningDetails;
 import android.content.res.Configuration;
 import android.graphics.Color;
@@ -291,6 +292,30 @@
     }
 
     @Test
+    public void testFindTopNonFinishingActivity_ignoresLaunchedFromBubbleActivities() {
+        final ActivityOptions opts = ActivityOptions.makeBasic();
+        opts.setTaskAlwaysOnTop(true);
+        opts.setLaunchedFromBubble(true);
+        ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID).setActivityOptions(opts).build();
+        mTaskFragment.addChild(activity);
+
+        assertNull(mTaskFragment.getTopNonFinishingActivity(true, false));
+    }
+
+    @Test
+    public void testFindTopNonFinishingActivity_includesLaunchedFromBubbleActivities() {
+        final ActivityOptions opts = ActivityOptions.makeBasic();
+        opts.setTaskAlwaysOnTop(true);
+        opts.setLaunchedFromBubble(true);
+        ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID).setActivityOptions(opts).build();
+        mTaskFragment.addChild(activity);
+
+        assertEquals(mTaskFragment.getTopNonFinishingActivity(true, true), activity);
+    }
+
+    @Test
     public void testMoveTaskToFront_supportsEnterPipOnTaskSwitchForAdjacentTaskFragment() {
         final Task bottomTask = createTask(mDisplayContent);
         final ActivityRecord bottomActivity = createActivityRecord(bottomTask);