Merge "Only provide DesktopTaskChangeListener if the flag is enabled." into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 35ac3ee..879c7a2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -8542,11 +8542,14 @@
     method public long getAudioHandle();
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public long getAvDataId();
+    method @FlaggedApi("android.media.tv.flags.tuner_w_apis") public int getDataGroupId();
     method public long getDataLength();
     method public long getDts();
     method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
+    method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @IntRange(from=0) public int getIndexInDataGroup();
     method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
     method @IntRange(from=0) public int getMpuSequenceNumber();
+    method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @IntRange(from=0) public int getNumDataPieces();
     method public long getOffset();
     method public long getPts();
     method public int getScIndexMask();
diff --git a/core/java/android/hardware/DisplayLuts.java b/core/java/android/hardware/DisplayLuts.java
new file mode 100644
index 0000000..b162ad6
--- /dev/null
+++ b/core/java/android/hardware/DisplayLuts.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.NonNull;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class DisplayLuts {
+    private IntArray mOffsets;
+    private int mTotalLength;
+
+    private List<float[]> mLutBuffers;
+    private IntArray mLutDimensions;
+    private IntArray mLutSizes;
+    private IntArray mLutSamplingKeys;
+    private static final int LUT_LENGTH_LIMIT = 100000;
+
+    public DisplayLuts() {
+        mOffsets = new IntArray();
+        mTotalLength = 0;
+
+        mLutBuffers = new ArrayList<>();
+        mLutDimensions = new IntArray();
+        mLutSizes = new IntArray();
+        mLutSamplingKeys = new IntArray();
+    }
+
+    /**
+     * Add the lut to be applied.
+     *
+     * @param buffer
+     * @param dimension either 1D or 3D
+     * @param size
+     * @param samplingKey
+     */
+    public void addLut(@NonNull float[] buffer, @LutProperties.Dimension int dimension,
+                       int size, @LutProperties.SamplingKey int samplingKey) {
+
+        int lutLength = 0;
+        if (dimension == LutProperties.ONE_DIMENSION) {
+            lutLength = size;
+        } else if (dimension == LutProperties.THREE_DIMENSION) {
+            lutLength = size * size * size;
+        } else {
+            clear();
+            throw new IllegalArgumentException("The dimension is either 1D or 3D!");
+        }
+
+        if (lutLength >= LUT_LENGTH_LIMIT) {
+            clear();
+            throw new IllegalArgumentException("The lut length is too big to handle!");
+        }
+
+        mOffsets.add(mTotalLength);
+        mTotalLength += lutLength;
+
+        mLutBuffers.add(buffer);
+        mLutDimensions.add(dimension);
+        mLutSizes.add(size);
+        mLutSamplingKeys.add(samplingKey);
+    }
+
+    private void clear() {
+        mTotalLength = 0;
+        mOffsets.clear();
+        mLutBuffers.clear();
+        mLutDimensions.clear();
+        mLutSamplingKeys.clear();
+    }
+
+    /**
+     * @return the array of Lut buffers
+     */
+    public float[] getLutBuffers() {
+        float[] buffer = new float[mTotalLength];
+
+        for (int i = 0; i < mLutBuffers.size(); i++) {
+            float[] lutBuffer = mLutBuffers.get(i);
+            System.arraycopy(lutBuffer, 0, buffer, mOffsets.get(i), lutBuffer.length);
+        }
+        return buffer;
+    }
+
+    /**
+     * @return the starting point of each lut memory region of the lut buffer
+     */
+    public int[] getOffsets() {
+        return mOffsets.toArray();
+    }
+
+    /**
+     * @return the array of Lut size
+     */
+    public int[] getLutSizes() {
+        return mLutSizes.toArray();
+    }
+
+    /**
+     * @return the array of Lut dimension
+     */
+    public int[] getLutDimensions() {
+        return mLutDimensions.toArray();
+    }
+
+    /**
+     * @return the array of sampling key
+     */
+    public int[] getLutSamplingKeys() {
+        return mLutSamplingKeys.toArray();
+    }
+}
diff --git a/core/java/android/hardware/LutProperties.java b/core/java/android/hardware/LutProperties.java
new file mode 100644
index 0000000..57f8a4e
--- /dev/null
+++ b/core/java/android/hardware/LutProperties.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Lut properties class.
+ *
+ * A Lut (Look-Up Table) is a pre-calculated table for color transformation.
+ *
+ * @hide
+ */
+public final class LutProperties {
+    private final @Dimension int mDimension;
+    private final long mSize;
+    private final @SamplingKey int[] mSamplingKeys;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SAMPLING_KEY_"}, value = {
+        SAMPLING_KEY_RGB,
+        SAMPLING_KEY_MAX_RGB
+    })
+    public @interface SamplingKey {
+    }
+
+    /** use r,g,b channel as the gain value of a Lut */
+    public static final int SAMPLING_KEY_RGB = 0;
+
+    /** use max of r,g,b channel as the gain value of a Lut */
+    public static final int SAMPLING_KEY_MAX_RGB = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+        ONE_DIMENSION,
+        THREE_DIMENSION
+    })
+    public @interface Dimension {
+    }
+
+    /** The Lut is one dimensional */
+    public static final int ONE_DIMENSION = 1;
+
+    /** The Lut is three dimensional */
+    public static final int THREE_DIMENSION = 3;
+
+    public @Dimension int getDimension() {
+        return mDimension;
+    }
+
+    /**
+     * @return the size of the Lut.
+     */
+    public long getSize() {
+        return mSize;
+    }
+
+    /**
+     * @return the list of sampling keys
+     */
+    public @SamplingKey int[] getSamplingKeys() {
+        if (mSamplingKeys.length == 0) {
+            throw new IllegalStateException("no sampling key!");
+        }
+        return mSamplingKeys;
+    }
+
+    /* use in the native code */
+    private LutProperties(@Dimension int dimension, long size, @SamplingKey int[] samplingKeys) {
+        if (dimension != ONE_DIMENSION || dimension != THREE_DIMENSION) {
+            throw new IllegalArgumentException("The dimension is either 1 or 3!");
+        }
+        mDimension = dimension;
+        mSize = size;
+        mSamplingKeys = samplingKeys;
+    }
+}
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 7b452a8..24cfc1b 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -50,6 +50,8 @@
     // Invoked on destruction
     private Runnable mCloser;
 
+    private LutProperties[] mLutProperties;
+
     private OverlayProperties(long nativeObject) {
         if (nativeObject != 0) {
             mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
@@ -70,6 +72,20 @@
     }
 
     /**
+     * Gets the lut properties of the display.
+     * @hide
+     */
+    public LutProperties[] getLutProperties() {
+        if (mNativeObject == 0) {
+            return null;
+        }
+        if (mLutProperties == null) {
+            mLutProperties = nGetLutProperties(mNativeObject);
+        }
+        return mLutProperties;
+    }
+
+    /**
      * Indicates that hardware composition of a buffer encoded with the provided {@link DataSpace}
      * and {@link HardwareBuffer.Format} is supported on the device.
      *
@@ -140,4 +156,5 @@
             long nativeObject, int dataspace, int format);
     private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
     private static native long nReadOverlayPropertiesFromParcel(Parcel in);
-}
+    private static native LutProperties[] nGetLutProperties(long nativeObject);
+}
\ No newline at end of file
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2748e32..19e244a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -49,6 +49,7 @@
 import android.gui.StalledTransactionInfo;
 import android.gui.TrustedOverlay;
 import android.hardware.DataSpace;
+import android.hardware.DisplayLuts;
 import android.hardware.HardwareBuffer;
 import android.hardware.OverlayProperties;
 import android.hardware.SyncFence;
@@ -307,9 +308,9 @@
     private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
     private static native void nativeSetDesiredPresentTimeNanos(long transactionObj,
                                                                 long desiredPresentTimeNanos);
-    private static native void nativeSetFrameTimeline(long transactionObj,
-                                                           long vsyncId);
     private static native void nativeNotifyShutdown();
+    private static native void nativeSetLuts(long transactionObj, long nativeObject,
+            float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4399,6 +4400,17 @@
             return this;
         }
 
+        /** @hide */
+        public @NonNull Transaction setLuts(@NonNull SurfaceControl sc,
+                @NonNull DisplayLuts displayLuts) {
+            checkPreconditions(sc);
+
+            nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(),
+                    displayLuts.getOffsets(), displayLuts.getLutDimensions(),
+                    displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys());
+            return this;
+        }
+
         /**
          * Sets the caching hint for the layer. By default, the caching hint is
          * {@link CACHING_ENABLED}.
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 96494b1..63de195 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "OverlayProperties"
 // #define LOG_NDEBUG 0
 
+#include <android/gui/LutProperties.h>
 #include <android/gui/OverlayProperties.h>
 #include <binder/Parcel.h>
 #include <gui/SurfaceComposerClient.h>
@@ -35,6 +36,12 @@
     jclass clazz;
     jmethodID ctor;
 } gOverlayPropertiesClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gLutPropertiesClassInfo;
+
 // ----------------------------------------------------------------------------
 // OverlayProperties lifecycle
 // ----------------------------------------------------------------------------
@@ -95,6 +102,36 @@
     return reinterpret_cast<jlong>(overlayProperties);
 }
 
+static jobjectArray android_hardware_OverlayProperties_getLutProperties(JNIEnv* env, jobject thiz,
+                                                                        jlong nativeObject) {
+    gui::OverlayProperties* overlayProperties =
+            reinterpret_cast<gui::OverlayProperties*>(nativeObject);
+    if (overlayProperties->lutProperties.has_value()) {
+        return NULL;
+    }
+    auto& lutProperties = overlayProperties->lutProperties.value();
+    if (lutProperties.empty()) {
+        return NULL;
+    }
+    int32_t size = static_cast<int32_t>(lutProperties.size());
+    jobjectArray nativeLutProperties =
+            env->NewObjectArray(size, gLutPropertiesClassInfo.clazz, NULL);
+    if (nativeLutProperties == NULL) {
+        return NULL;
+    }
+    for (int32_t i = 0; i < size; i++) {
+        if (lutProperties[i].has_value()) {
+            auto& item = lutProperties[i].value();
+            jobject properties =
+                    env->NewObject(gLutPropertiesClassInfo.clazz, gLutPropertiesClassInfo.ctor,
+                                   static_cast<int32_t>(item.dimension), item.size,
+                                   item.samplingKeys.data());
+            env->SetObjectArrayElement(nativeLutProperties, i, properties);
+        }
+    }
+    return nativeLutProperties;
+}
+
 // ----------------------------------------------------------------------------
 // Serialization
 // ----------------------------------------------------------------------------
@@ -161,6 +198,8 @@
     { "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
             (void*) android_hardware_OverlayProperties_read },
     {"nCreateDefault", "()J", (void*) android_hardware_OverlayProperties_createDefault },
+    {"nGetLutProperties", "(J)[Landroid/hardware/LutProperties;",
+            (void*) android_hardware_OverlayProperties_getLutProperties },
 };
 // clang-format on
 
@@ -171,5 +210,9 @@
     gOverlayPropertiesClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
     gOverlayPropertiesClassInfo.ctor =
             GetMethodIDOrDie(env, gOverlayPropertiesClassInfo.clazz, "<init>", "(J)V");
+    clazz = FindClassOrDie(env, "android/hardware/LutProperties");
+    gLutPropertiesClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gLutPropertiesClassInfo.ctor =
+            GetMethodIDOrDie(env, gLutPropertiesClassInfo.clazz, "<init>", "(IJ[I)V");
     return err;
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 71ba214..a939d92 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -33,11 +33,13 @@
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_view_SurfaceControl.h>
 #include <android_runtime/android_view_SurfaceSession.h>
+#include <cutils/ashmem.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <private/gui/ComposerService.h>
 #include <stdio.h>
@@ -736,6 +738,65 @@
     transaction->setDesiredHdrHeadroom(ctrl, desiredRatio);
 }
 
+static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                          jfloatArray jbufferArray, jintArray joffsetArray,
+                          jintArray jdimensionArray, jintArray jsizeArray,
+                          jintArray jsamplingKeyArray) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+
+    ScopedIntArrayRW joffsets(env, joffsetArray);
+    if (joffsets.get() == nullptr) {
+        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+        return;
+    }
+    ScopedIntArrayRW jdimensions(env, jdimensionArray);
+    if (jdimensions.get() == nullptr) {
+        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+        return;
+    }
+    ScopedIntArrayRW jsizes(env, jsizeArray);
+    if (jsizes.get() == nullptr) {
+        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+        return;
+    }
+    ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+    if (jsamplingKeys.get() == nullptr) {
+        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+        return;
+    }
+
+    jsize numLuts = env->GetArrayLength(jdimensionArray);
+    std::vector<int32_t> offsets(joffsets.get(), joffsets.get() + numLuts);
+    std::vector<int32_t> dimensions(jdimensions.get(), jdimensions.get() + numLuts);
+    std::vector<int32_t> sizes(jsizes.get(), jsizes.get() + numLuts);
+    std::vector<int32_t> samplingKeys(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
+
+    ScopedFloatArrayRW jbuffers(env, jbufferArray);
+    if (jbuffers.get() == nullptr) {
+        jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+        return;
+    }
+
+    // create the shared memory and copy jbuffers
+    size_t bufferSize = jbuffers.size() * sizeof(float);
+    int32_t fd = ashmem_create_region("lut_shread_mem", bufferSize);
+    if (fd < 0) {
+        jniThrowRuntimeException(env, "ashmem_create_region() failed");
+        return;
+    }
+    void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (ptr == MAP_FAILED) {
+        jniThrowRuntimeException(env, "Failed to map the shared memory");
+        return;
+    }
+    memcpy(ptr, jbuffers.get(), bufferSize);
+    // unmap
+    munmap(ptr, bufferSize);
+
+    transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
+}
+
 static void nativeSetCachingHint(JNIEnv* env, jclass clazz, jlong transactionObj,
                                  jlong nativeObject, jint cachingHint) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2541,6 +2602,7 @@
             (void*) nativeSetDesiredPresentTimeNanos },
     {"nativeNotifyShutdown", "()V",
             (void*)nativeNotifyShutdown },
+    {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts },
         // clang-format on
 };
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
index 5876682..85dabce 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
@@ -30,29 +30,32 @@
  */
 public class BubbleInfo implements Parcelable {
 
-    private String mKey; // Same key as the Notification
+    private final String mKey; // Same key as the Notification
     private int mFlags;  // Flags from BubbleMetadata
     @Nullable
-    private String mShortcutId;
-    private int mUserId;
-    private String mPackageName;
+    private final String mShortcutId;
+    private final int mUserId;
+    private final String mPackageName;
     /**
      * All notification bubbles require a shortcut to be set on the notification, however, the
      * app could still specify an Icon and PendingIntent to use for the bubble. In that case
      * this icon will be populated. If the bubble is entirely shortcut based, this will be null.
      */
     @Nullable
-    private Icon mIcon;
+    private final Icon mIcon;
     @Nullable
-    private String mTitle;
+    private final String mTitle;
     @Nullable
-    private String mAppName;
-    private boolean mIsImportantConversation;
-    private boolean mShowAppBadge;
+    private final String mAppName;
+    private final boolean mIsImportantConversation;
+    private final boolean mShowAppBadge;
+    @Nullable
+    private final ParcelableFlyoutMessage mParcelableFlyoutMessage;
 
     public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon,
             int userId, String packageName, @Nullable String title, @Nullable String appName,
-            boolean isImportantConversation, boolean showAppBadge) {
+            boolean isImportantConversation, boolean showAppBadge,
+            @Nullable ParcelableFlyoutMessage flyoutMessage) {
         mKey = key;
         mFlags = flags;
         mShortcutId = shortcutId;
@@ -63,6 +66,7 @@
         mAppName = appName;
         mIsImportantConversation = isImportantConversation;
         mShowAppBadge = showAppBadge;
+        mParcelableFlyoutMessage = flyoutMessage;
     }
 
     private BubbleInfo(Parcel source) {
@@ -76,6 +80,8 @@
         mAppName = source.readString();
         mIsImportantConversation = source.readBoolean();
         mShowAppBadge = source.readBoolean();
+        mParcelableFlyoutMessage = source.readParcelable(
+                ParcelableFlyoutMessage.class.getClassLoader(), ParcelableFlyoutMessage.class);
     }
 
     public String getKey() {
@@ -122,6 +128,11 @@
         return mShowAppBadge;
     }
 
+    @Nullable
+    public ParcelableFlyoutMessage getParcelableFlyoutMessage() {
+        return mParcelableFlyoutMessage;
+    }
+
     /**
      * Whether this bubble is currently being hidden from the stack.
      */
@@ -180,6 +191,7 @@
         parcel.writeString(mAppName);
         parcel.writeBoolean(mIsImportantConversation);
         parcel.writeBoolean(mShowAppBadge);
+        parcel.writeParcelable(mParcelableFlyoutMessage, flags);
     }
 
     @NonNull
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt
new file mode 100644
index 0000000..294d5e5
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared.bubbles
+
+import android.graphics.drawable.Icon
+import android.os.Parcel
+import android.os.Parcelable
+
+/** The contents of the flyout message to be passed to launcher for rendering in the bubble bar. */
+class ParcelableFlyoutMessage(
+    val icon: Icon?,
+    val title: String?,
+    val message: String?,
+) : Parcelable {
+
+    constructor(
+        parcel: Parcel
+    ) : this(
+        icon = parcel.readParcelable(Icon::class.java.classLoader),
+        title = parcel.readString(),
+        message = parcel.readString(),
+    )
+
+    override fun writeToParcel(parcel: Parcel, flags: Int) {
+        parcel.writeParcelable(icon, flags)
+        parcel.writeString(title)
+        parcel.writeString(message)
+    }
+
+    override fun describeContents() = 0
+
+    companion object {
+        @JvmField
+        val CREATOR =
+            object : Parcelable.Creator<ParcelableFlyoutMessage> {
+                override fun createFromParcel(parcel: Parcel) = ParcelableFlyoutMessage(parcel)
+
+                override fun newArray(size: Int) = arrayOfNulls<ParcelableFlyoutMessage>(size)
+            }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 169361a..e3fc5c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -56,6 +56,7 @@
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.bubbles.BubbleInfo;
+import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -350,7 +351,22 @@
                 getTitle(),
                 getAppName(),
                 isImportantConversation(),
-                !isAppLaunchIntent());
+                !isAppLaunchIntent(),
+                getParcelableFlyoutMessage());
+    }
+
+    /** Creates a parcelable flyout message to send to launcher. */
+    @Nullable
+    private ParcelableFlyoutMessage getParcelableFlyoutMessage() {
+        if (mFlyoutMessage == null) {
+            return null;
+        }
+        // the icon is only used in group chats
+        Icon icon = mFlyoutMessage.isGroupChat ? mFlyoutMessage.senderIcon : null;
+        String title =
+                mFlyoutMessage.senderName == null ? null : mFlyoutMessage.senderName.toString();
+        String message = mFlyoutMessage.message == null ? null : mFlyoutMessage.message.toString();
+        return new ParcelableFlyoutMessage(icon, title, message);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 3982a23..c5e3afd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -274,7 +274,7 @@
         @Nullable BubbleExpandedView expandedView;
         int dotColor;
         Path dotPath;
-        @Nullable Bubble.FlyoutMessage flyoutMessage;
+        Bubble.FlyoutMessage flyoutMessage;
         Bitmap bubbleBitmap;
         Bitmap badgeBitmap;
 
@@ -300,6 +300,10 @@
                 return null;
             }
 
+            // set the flyout message but don't load the avatar because we can't pass it on the
+            // binder to launcher
+            info.flyoutMessage = b.getFlyoutMessage();
+
             return info;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
index 1b7bb0d..c12822a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
@@ -181,7 +181,7 @@
         @Nullable BubbleExpandedView expandedView;
         int dotColor;
         Path dotPath;
-        @Nullable Bubble.FlyoutMessage flyoutMessage;
+        Bubble.FlyoutMessage flyoutMessage;
         Bitmap bubbleBitmap;
         Bitmap badgeBitmap;
 
@@ -221,6 +221,10 @@
                 return null;
             }
 
+            // set the flyout message but don't load the avatar because we can't pass it on the
+            // binder to launcher
+            info.flyoutMessage = b.getFlyoutMessage();
+
             return info;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 7ba7034..79c31e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -356,6 +356,7 @@
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopTasksController> desktopTasksController,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
@@ -364,7 +365,8 @@
                 ? shellInit
                 : null;
         return new FreeformTaskListener(context, init, shellTaskOrganizer,
-                desktopRepository, launchAdjacentController, windowDecorViewModel);
+                desktopRepository, desktopTasksController, launchAdjacentController,
+                windowDecorViewModel);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4e548a6..5b9d2fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -110,6 +110,7 @@
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import com.android.wm.shell.windowdecor.extension.isFullscreen
 import com.android.wm.shell.windowdecor.extension.isMultiWindow
+import com.android.wm.shell.windowdecor.extension.requestingImmersive
 import java.io.PrintWriter
 import java.util.Optional
 import java.util.concurrent.Executor
@@ -1844,6 +1845,17 @@
         userId = newUserId
     }
 
+    /** Called when a task's info changes. */
+    fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
+        if (!Flags.enableFullyImmersiveInDesktop()) return
+        val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)
+        val requestingImmersive = taskInfo.requestingImmersive
+        if (inImmersive && !requestingImmersive) {
+            // Exit immersive if the app is no longer requesting it.
+            exitDesktopTaskFromFullImmersive(taskInfo)
+        }
+    }
+
     private fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
         pw.println("${prefix}DesktopTasksController")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 73f7011..fbd3c10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -30,6 +30,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
@@ -50,6 +51,7 @@
     private final Context mContext;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final Optional<DesktopRepository> mDesktopRepository;
+    private final Optional<DesktopTasksController> mDesktopTasksController;
     private final WindowDecorViewModel mWindowDecorationViewModel;
     private final LaunchAdjacentController mLaunchAdjacentController;
 
@@ -65,12 +67,14 @@
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopTasksController> desktopTasksController,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorationViewModel) {
         mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
         mDesktopRepository = desktopRepository;
+        mDesktopTasksController = desktopTasksController;
         mLaunchAdjacentController = launchAdjacentController;
         if (shellInit != null) {
             shellInit.addInitCallback(this::onInit, this);
@@ -147,6 +151,7 @@
 
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
                 taskInfo.taskId);
+        mDesktopTasksController.ifPresent(c -> c.onTaskInfoChanged(taskInfo));
         mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
         state.mTaskInfo = taskInfo;
         if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9e5c1a6..ae4772e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -50,6 +50,7 @@
 import android.view.DragEvent
 import android.view.Gravity
 import android.view.SurfaceControl
+import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_CLOSE
@@ -75,6 +76,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
 import com.android.wm.shell.MockToken
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -142,6 +144,7 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.times
@@ -3151,6 +3154,30 @@
     })
   }
 
+  @Test
+  @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+  fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
+    val task = setUpFreeformTask(DEFAULT_DISPLAY)
+    taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
+
+    task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+    controller.onTaskInfoChanged(task)
+
+    verify(mockDesktopFullImmersiveTransitionHandler).exitImmersive(eq(task), any())
+  }
+
+  @Test
+  @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+  fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() {
+    val task = setUpFreeformTask(DEFAULT_DISPLAY)
+    taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false)
+
+    task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+    controller.onTaskInfoChanged(task)
+
+    verify(mockDesktopFullImmersiveTransitionHandler, never()).exitImmersive(eq(task), any())
+  }
+
   /**
    * Assert that an unhandled drag event launches a PendingIntent with the
    * windowing mode and bounds we are expecting.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 956ef14..36e0427 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -42,6 +42,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -75,6 +76,8 @@
     @Mock
     private DesktopRepository mDesktopRepository;
     @Mock
+    private DesktopTasksController mDesktopTasksController;
+    @Mock
     private LaunchAdjacentController mLaunchAdjacentController;
     private FreeformTaskListener mFreeformTaskListener;
     private StaticMockitoSession mMockitoSession;
@@ -90,6 +93,7 @@
                 mShellInit,
                 mTaskOrganizer,
                 Optional.of(mDesktopRepository),
+                Optional.of(mDesktopTasksController),
                 mLaunchAdjacentController,
                 mWindowDecorViewModel);
     }
@@ -177,6 +181,18 @@
         verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
     }
 
+    @Test
+    public void onTaskInfoChanged_withDesktopController_forwards() {
+        ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        task.isVisible = true;
+        mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+        mFreeformTaskListener.onTaskInfoChanged(task);
+
+        verify(mDesktopTasksController).onTaskInfoChanged(task);
+    }
+
     @After
     public void tearDown() {
         mMockitoSession.finishMocking();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
index 641063c..205defe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.shared.bubbles
 
+import android.graphics.drawable.Icon
+import android.net.Uri
 import android.os.Parcel
 import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE
 import android.testing.AndroidTestingRunner
@@ -42,7 +44,12 @@
                 "title",
                 "Some app",
                 true,
-                true
+                true,
+                ParcelableFlyoutMessage(
+                    Icon.createWithContentUri(Uri.parse("content://image/123")),
+                    "sender",
+                    "message"
+                )
             )
         val parcel = Parcel.obtain()
         bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE)
@@ -60,5 +67,10 @@
         assertThat(bubbleInfo.appName).isEqualTo(bubbleInfoFromParcel.appName)
         assertThat(bubbleInfo.isImportantConversation)
             .isEqualTo(bubbleInfoFromParcel.isImportantConversation)
+        with(bubbleInfo.parcelableFlyoutMessage!!) {
+            assertThat(icon!!.uri.toString()).isEqualTo("content://image/123")
+            assertThat(title).isEqualTo("sender")
+            assertThat(message).isEqualTo("message")
+        }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 4676dff..84a13ab 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -17,12 +17,14 @@
 package android.media.tv.tuner.filter;
 
 import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.media.AudioPresentation;
 import android.media.MediaCodec.LinearBlock;
+import android.media.tv.flags.Flags;
 
 import java.util.Collections;
 import java.util.List;
@@ -57,12 +59,16 @@
     private final int mScIndexMask;
     private final AudioDescriptor mExtraMetaData;
     private final List<AudioPresentation> mAudioPresentations;
+    private final int mNumDataPieces;
+    private final int mIndexInDataGroup;
+    private final int mDataGroupId;
 
     // This constructor is used by JNI code only
     private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
             long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
             int mpuSequenceNumber, boolean isPrivateData, int scIndexMask,
-            AudioDescriptor extraMetaData, List<AudioPresentation> audioPresentations) {
+            AudioDescriptor extraMetaData, List<AudioPresentation> audioPresentations,
+            int numDataPieces, int indexInDataGroup, int dataGroupId) {
         mStreamId = streamId;
         mIsPtsPresent = isPtsPresent;
         mPts = pts;
@@ -78,6 +84,9 @@
         mScIndexMask = scIndexMask;
         mExtraMetaData = extraMetaData;
         mAudioPresentations = audioPresentations;
+        mNumDataPieces = numDataPieces;
+        mIndexInDataGroup = indexInDataGroup;
+        mDataGroupId = dataGroupId;
     }
 
     /**
@@ -235,6 +244,67 @@
     }
 
     /**
+     * Gets the number of data pieces into which the original data was split.
+     *
+     * <p>The {@link #getNumDataPieces()}, {@link #getIndexInDataGroup()} and
+     * {@link #getDataGroupId()} methods should be used together to reassemble the original data if
+     * it was split into pieces. Use {@link #getLinearBlock()} to get the memory where the data
+     * pieces are stored.
+     *
+     * @return 0 or 1 if this MediaEvent object contains the complete data; otherwise the number of
+     *         pieces into which the original data was split.
+     * @see #getIndexInDataGroup()
+     * @see #getDataGroupId()
+     * @see #getLinearBlock()
+     */
+    @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+    @IntRange(from = 0)
+    public int getNumDataPieces() {
+        return mNumDataPieces;
+    }
+
+    /**
+     * Gets the index of the data piece. The index in the data group indicates the order in which
+     * this {@link MediaEvent}'s data piece should be reassembled. The result should be within the
+     * range [0, {@link #getNumDataPieces()}).
+     *
+     * <p>The {@link #getNumDataPieces()}, {@link #getIndexInDataGroup()} and
+     * {@link #getDataGroupId()} methods should be used together to reassemble the original data if
+     * it was split into pieces. Use {@link #getLinearBlock()} to get the memory where the data
+     * pieces are stored.
+     *
+     * @return The index in the data group.
+     * @see #getNumDataPieces()
+     * @see #getDataGroupId()
+     * @see #getLinearBlock()
+     */
+    @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+    @IntRange(from = 0)
+    public int getIndexInDataGroup() {
+        return mIndexInDataGroup;
+    }
+
+    /**
+     * Gets the group ID for reassembling the complete data. {@link MediaEvent}s that have the same
+     * data group ID contain different pieces of the same data. This value should be ignored if
+     * {@link #getNumDataPieces()} returns 0 or 1.
+     *
+     * <p>The {@link #getNumDataPieces()}, {@link #getIndexInDataGroup()} and
+     * {@link #getDataGroupId()} methods should be used together to reassemble the original data if
+     * it was split into pieces. Use {@link #getLinearBlock()} to get the memory where the data
+     * pieces are stored.
+     *
+     * @return The data group ID.
+     * @see #getNumDataPieces()
+     * @see #getIndexInDataGroup()
+     * @see #getLinearBlock()
+     */
+    @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+    public int getDataGroupId() {
+        return mDataGroupId;
+    }
+
+    /**
      * Finalize the MediaEvent object.
      * @hide
      */
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 00b0e57..49e7941 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -686,12 +686,16 @@
     } else if (mediaEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scVvc) {
         sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
     }
+    jint numDataPieces = mediaEvent.numDataPieces;
+    jint indexInDataGroup = mediaEvent.indexInDataGroup;
+    jint dataGroupId = mediaEvent.dataGroupId;
 
     ScopedLocalRef obj(env, env->NewObject(mMediaEventClass, mMediaEventInitID, streamId,
                                            isPtsPresent, pts, isDtsPresent, dts, dataLength,
                                            offset, nullptr, isSecureMemory, avDataId,
                                            mpuSequenceNumber, isPesPrivateData, sc,
-                                           audioDescriptor.get(), presentationsJObj.get()));
+                                           audioDescriptor.get(), presentationsJObj.get(),
+                                           numDataPieces, indexInDataGroup, dataGroupId));
 
     // Protect mFilterClient from being set to null.
     android::Mutex::Autolock autoLock(mLock);
@@ -1048,7 +1052,7 @@
             "<init>",
             "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
             "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
-            "Ljava/util/List;)V");
+            "Ljava/util/List;III)V");
     mAudioDescriptorInitID = env->GetMethodID(mAudioDescriptorClass, "<init>", "(BBCBBB)V");
     mPesEventInitID = env->GetMethodID(mPesEventClass, "<init>", "(III)V");
     mTsRecordEventInitID = env->GetMethodID(mTsRecordEventClass, "<init>", "(IIIJJI)V");
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 97d89a2..afa92f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -83,11 +83,7 @@
             }
 
         val state = remember {
-            MutableSceneTransitionLayoutState(
-                currentScene,
-                ClockTransition.defaultClockTransitions,
-                enableInterruptions = false,
-            )
+            MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
         }
 
         // Update state whenever currentSceneKey has changed.
@@ -102,7 +98,7 @@
                 scene(splitShadeLargeClockScene) {
                     LargeClockWithSmartSpace(
                         smartSpacePaddingTop = smartSpacePaddingTop,
-                        shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
+                        shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation,
                     )
                 }
 
@@ -114,21 +110,15 @@
                 }
 
                 scene(smallClockScene) {
-                    SmallClockWithSmartSpace(
-                        smartSpacePaddingTop = smartSpacePaddingTop,
-                    )
+                    SmallClockWithSmartSpace(smartSpacePaddingTop = smartSpacePaddingTop)
                 }
 
                 scene(largeClockScene) {
-                    LargeClockWithSmartSpace(
-                        smartSpacePaddingTop = smartSpacePaddingTop,
-                    )
+                    LargeClockWithSmartSpace(smartSpacePaddingTop = smartSpacePaddingTop)
                 }
 
                 scene(WeatherClockScenes.largeClockScene) {
-                    WeatherLargeClockWithSmartSpace(
-                        smartSpacePaddingTop = smartSpacePaddingTop,
-                    )
+                    WeatherLargeClockWithSmartSpace(smartSpacePaddingTop = smartSpacePaddingTop)
                 }
 
                 scene(WeatherClockScenes.splitShadeLargeClockScene) {
@@ -154,7 +144,7 @@
                 SmallClock(
                     burnInParams = burnIn.parameters,
                     onTopChanged = burnIn.onSmallClockTopChanged,
-                    modifier = Modifier.wrapContentSize()
+                    modifier = Modifier.wrapContentSize(),
                 )
             }
             with(smartSpaceSection) {
@@ -202,7 +192,7 @@
                                     y = 0,
                                 )
                             }
-                        }
+                        },
                 )
             }
         }
@@ -226,10 +216,7 @@
         Column(modifier = modifier) {
             val currentClock = currentClockState.value ?: return@Column
             with(weatherClockSection) {
-                Time(
-                    clock = currentClock,
-                    burnInParams = burnIn.parameters,
-                )
+                Time(clock = currentClock, burnInParams = burnIn.parameters)
             }
             val density = LocalDensity.current
             val context = LocalContext.current
@@ -242,7 +229,7 @@
                     modifier =
                         Modifier.heightIn(
                             min = getDimen(context, "enhanced_smartspace_height", density)
-                        )
+                        ),
                 )
             }
             with(weatherClockSection) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 40b2a08..a0c56b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -139,7 +139,7 @@
                 TransitionStep(
                     KeyguardState.OFF,
                     KeyguardState.LOCKSCREEN,
-                    transitionState = TransitionState.STARTED
+                    transitionState = TransitionState.STARTED,
                 )
             )
 
@@ -181,7 +181,7 @@
                 TransitionStep(
                     KeyguardState.AOD,
                     KeyguardState.LOCKSCREEN,
-                    transitionState = TransitionState.STARTED
+                    transitionState = TransitionState.STARTED,
                 )
             )
 
@@ -206,7 +206,7 @@
                 TransitionStep(
                     KeyguardState.DOZING,
                     KeyguardState.LOCKSCREEN,
-                    transitionState = TransitionState.STARTED
+                    transitionState = TransitionState.STARTED,
                 )
             )
 
@@ -229,7 +229,7 @@
                 TransitionStep(
                     KeyguardState.DOZING,
                     KeyguardState.LOCKSCREEN,
-                    transitionState = TransitionState.STARTED
+                    transitionState = TransitionState.STARTED,
                 )
             )
 
@@ -244,12 +244,14 @@
     fun faceAuthLockedOutStateIsUpdatedAfterUserSwitch() =
         testScope.runTest {
             underTest.start()
+            runCurrent()
+            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
 
             // User switching has started
             fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
             fakeUserRepository.setSelectedUserInfo(
                 primaryUser,
-                SelectionStatus.SELECTION_IN_PROGRESS
+                SelectionStatus.SELECTION_IN_PROGRESS,
             )
             runCurrent()
 
@@ -258,7 +260,7 @@
             facePropertyRepository.setLockoutMode(secondaryUser.id, LockoutMode.NONE)
             fakeUserRepository.setSelectedUserInfo(
                 secondaryUser,
-                SelectionStatus.SELECTION_COMPLETE
+                SelectionStatus.SELECTION_COMPLETE,
             )
             runCurrent()
 
@@ -316,7 +318,7 @@
                 .isEqualTo(
                     Pair(
                         FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
-                        false
+                        false,
                     )
                 )
         }
@@ -600,7 +602,7 @@
 
             faceAuthRepository.requestAuthenticate(
                 FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
-                true
+                true,
             )
             facePropertyRepository.setCameraIno(CameraInfo("0", "1", null))
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 17e3006..047d8c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -42,7 +42,8 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
@@ -57,7 +58,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito
 import org.mockito.Mockito.reset
 
 @ExperimentalCoroutinesApi
@@ -66,7 +66,7 @@
 class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            this.fakeKeyguardTransitionRepository = Mockito.spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
     private val testScope = kosmos.testScope
     private lateinit var underTest: FromAlternateBouncerTransitionInteractor
@@ -74,7 +74,7 @@
 
     @Before
     fun setup() {
-        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
         underTest = kosmos.fromAlternateBouncerTransitionInteractor
         underTest.start()
     }
@@ -86,7 +86,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.OCCLUDED,
                 to = KeyguardState.ALTERNATE_BOUNCER,
-                testScope
+                testScope,
             )
             reset(transitionRepository)
 
@@ -111,7 +111,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.OCCLUDED,
                 to = KeyguardState.ALTERNATE_BOUNCER,
-                testScope
+                testScope,
             )
             reset(transitionRepository)
 
@@ -129,7 +129,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.OCCLUDED,
                 to = KeyguardState.ALTERNATE_BOUNCER,
-                testScope
+                testScope,
             )
             reset(transitionRepository)
 
@@ -158,7 +158,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.OCCLUDED,
                 to = KeyguardState.ALTERNATE_BOUNCER,
-                testScope
+                testScope,
             )
             reset(transitionRepository)
 
@@ -168,7 +168,7 @@
             assertThat(transitionRepository)
                 .startedTransition(
                     from = KeyguardState.ALTERNATE_BOUNCER,
-                    to = KeyguardState.OCCLUDED
+                    to = KeyguardState.OCCLUDED,
                 )
         }
 
@@ -183,7 +183,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.GLANCEABLE_HUB,
                 to = KeyguardState.ALTERNATE_BOUNCER,
-                testScope
+                testScope,
             )
             reset(transitionRepository)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 33f3cd4..9300964 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -42,8 +42,9 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -69,7 +70,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -77,7 +77,7 @@
 class FromAodTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
 
     private val testScope = kosmos.testScope
@@ -89,7 +89,7 @@
     @Before
     fun setup() {
         powerInteractor = kosmos.powerInteractor
-        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
         underTest = kosmos.fromAodTransitionInteractor
 
         underTest.start()
@@ -101,7 +101,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.AOD,
-                testScope
+                testScope,
             )
             kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
             reset(transitionRepository)
@@ -117,10 +117,7 @@
 
             // Under default conditions, we should transition to LOCKSCREEN when waking up.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -133,10 +130,7 @@
 
             // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.OCCLUDED,
-                )
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
         }
 
     @Test
@@ -363,13 +357,13 @@
                         from = KeyguardState.GONE,
                         to = KeyguardState.AOD,
                         transitionState = TransitionState.STARTED,
-                        value = 0f
+                        value = 0f,
                     ),
                     TransitionStep(
                         from = KeyguardState.GONE,
                         to = KeyguardState.AOD,
                         transitionState = TransitionState.RUNNING,
-                        value = 0.1f
+                        value = 0.1f,
                     ),
                 ),
                 testScope = testScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index ff0a4a1..3b6e5d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -36,8 +36,9 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -79,7 +80,7 @@
 class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
             this.fakeCommunalSceneRepository =
                 spy(FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope))
         }
@@ -105,7 +106,7 @@
     @Before
     fun setup() {
         powerInteractor = kosmos.powerInteractor
-        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
         underTest = kosmos.fromDozingTransitionInteractor
 
         underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index fa304c9..9ca3ce6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -32,7 +32,9 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
@@ -53,7 +55,6 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -77,14 +78,21 @@
 
     private val kosmos =
         testKosmos().apply {
-            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.fakeKeyguardTransitionRepository =
+                FakeKeyguardTransitionRepository(
+                    // This test sends transition steps manually in the test cases.
+                    sendTransitionStepsOnStartTransition = false,
+                    testScope = testScope,
+                )
+
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
 
     private val testScope = kosmos.testScope
     private val underTest by lazy { kosmos.fromDreamingTransitionInteractor }
 
     private val powerInteractor = kosmos.powerInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
index af76b08..57b1299 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -23,10 +23,10 @@
 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.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -41,18 +41,17 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class FromGoneTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
     private val testScope = kosmos.testScope
     private val underTest = kosmos.fromGoneTransitionInteractor
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
 
     @Before
     fun setUp() {
@@ -101,9 +100,7 @@
 
             // We're in the middle of a GONE -> LOCKSCREEN transition.
             assertThat(keyguardTransitionRepository)
-                .startedTransition(
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -121,15 +118,13 @@
             kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
                 AuthenticationFlags(
                     0,
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+                    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,
-                )
+                .startedTransition(to = KeyguardState.LOCKSCREEN)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 4d81317..9c2e631 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,9 +27,11 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,10 +44,10 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -53,15 +55,20 @@
 class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
 
     private val testScope = kosmos.testScope
     private val underTest = kosmos.fromLockscreenTransitionInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
     private val shadeRepository = kosmos.fakeShadeRepository
     private val keyguardRepository = kosmos.fakeKeyguardRepository
 
+    @Before
+    fun setup() {
+        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
+    }
+
     @Test
     fun testSurfaceBehindVisibility() =
         testScope.runTest {
@@ -256,4 +263,43 @@
             assertThatRepository(transitionRepository)
                 .startedTransition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.DREAMING)
         }
+
+    @Test
+    fun testTransitionsBackToOccluded_ifOccluded_andCanceledSwipe() =
+        testScope.runTest {
+            underTest.start()
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            keyguardRepository.setKeyguardDismissible(false)
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            reset(transitionRepository)
+
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                )
+            reset(transitionRepository)
+
+            runCurrent()
+
+            shadeRepository.setLegacyShadeExpansion(0.6f)
+            shadeRepository.setLegacyShadeExpansion(0.7f)
+            runCurrent()
+
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 7424320..4a90722 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -42,9 +42,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
@@ -60,7 +60,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -68,14 +67,14 @@
 class FromOccludedTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
 
     private val testScope = kosmos.testScope
     private val underTest = kosmos.fromOccludedTransitionInteractor
 
     private val powerInteractor = kosmos.powerInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
 
     @Before
     fun setup() {
@@ -88,7 +87,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.OCCLUDED,
-                testScope
+                testScope,
             )
             reset(transitionRepository)
         }
@@ -102,10 +101,7 @@
             runCurrent()
 
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.OCCLUDED,
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -122,9 +118,6 @@
             runCurrent()
 
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.OCCLUDED,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                )
+                .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.GLANCEABLE_HUB)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 14f2d65..a7da230 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -26,9 +26,9 @@
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,20 +44,19 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
-    val kosmos =
+    private val kosmos =
         testKosmos().apply {
-            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
     val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
     val testScope = kosmos.testScope
     val selectedUserInteractor = kosmos.selectedUserInteractor
-    val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
     val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
 
     @Test
@@ -67,12 +66,7 @@
             runCurrent()
 
             // Transition-specific surface visibility should be null ("don't care") initially.
-            assertEquals(
-                listOf(
-                    null,
-                ),
-                values
-            )
+            assertEquals(listOf(null), values)
 
             transitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -86,9 +80,9 @@
 
             assertEquals(
                 listOf(
-                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+                    null // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -117,7 +111,7 @@
                     null,
                     false, // Surface is only made visible once the bouncer UI animates out.
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -137,7 +131,7 @@
                     false,
                     true, // Surface should eventually be visible.
                 ),
-                values
+                values,
             )
         }
 
@@ -150,7 +144,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.PRIMARY_BOUNCER,
-                testScope
+                testScope,
             )
 
             reset(transitionRepository)
@@ -161,7 +155,7 @@
             assertThat(transitionRepository)
                 .startedTransition(
                     from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.LOCKSCREEN
+                    to = KeyguardState.LOCKSCREEN,
                 )
         }
 
@@ -177,7 +171,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.PRIMARY_BOUNCER,
-                testScope
+                testScope,
             )
 
             reset(transitionRepository)
@@ -188,7 +182,7 @@
             assertThat(transitionRepository)
                 .startedTransition(
                     from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GLANCEABLE_HUB
+                    to = KeyguardState.GLANCEABLE_HUB,
                 )
         }
 
@@ -201,7 +195,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.PRIMARY_BOUNCER,
-                testScope
+                testScope,
             )
 
             reset(transitionRepository)
@@ -218,7 +212,7 @@
             assertThat(transitionRepository)
                 .startedTransition(
                     from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.OCCLUDED
+                    to = KeyguardState.OCCLUDED,
                 )
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a617484..8f3d549 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -41,9 +41,9 @@
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -76,7 +76,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -91,7 +90,7 @@
 class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
         }
     private val testScope = kosmos.testScope
 
@@ -99,7 +98,7 @@
     private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
     private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
-    private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepositorySpy }
     private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
new file mode 100644
index 0000000..22677b2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+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.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryBackgroundViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest: DeviceEntryBackgroundViewModel by lazy {
+        kosmos.deviceEntryBackgroundViewModel
+    }
+
+    @Test
+    fun lockscreenToDozingTransitionChangesBackgroundViewAlphaToZero() =
+        testScope.runTest {
+            kosmos.fingerprintPropertyRepository.supportsUdfps()
+            val alpha by collectLastValue(underTest.alpha)
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                listOf(dozingToLockscreen(0f, STARTED), dozingToLockscreen(0.1f)),
+                testScope,
+            )
+            runCurrent()
+            assertThat(alpha).isEqualTo(1.0f)
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                listOf(lockscreenToDozing(0f, STARTED)),
+                testScope,
+            )
+            runCurrent()
+
+            assertThat(alpha).isEqualTo(0.0f)
+        }
+
+    private fun lockscreenToDozing(value: Float, state: TransitionState = RUNNING): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DOZING,
+            value = value,
+            transitionState = state,
+            ownerName = "DeviceEntryBackgroundViewModelTest",
+        )
+    }
+
+    private fun dozingToLockscreen(value: Float, state: TransitionState = RUNNING): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.DOZING,
+            to = KeyguardState.LOCKSCREEN,
+            value = value,
+            transitionState = state,
+            ownerName = "DeviceEntryBackgroundViewModelTest",
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
index 3e5dee6..a1edfc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
@@ -53,7 +53,7 @@
     private val bgExecutor = kosmos.fakeExecutor
     private val userContextProvider: UserContextProvider = kosmos.userTracker
     private val dialogTransitionAnimator: DialogTransitionAnimator = kosmos.dialogTransitionAnimator
-    private lateinit var traceurMessageSender: TraceurMessageSender
+    private lateinit var traceurConnection: TraceurConnection
     private val issueRecordingState =
         IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
 
@@ -65,13 +65,13 @@
 
     @Before
     fun setup() {
-        traceurMessageSender = mock<TraceurMessageSender>()
+        traceurConnection = mock<TraceurConnection>()
         underTest =
             IssueRecordingServiceSession(
                 bgExecutor,
                 dialogTransitionAnimator,
                 panelInteractor,
-                traceurMessageSender,
+                traceurConnection,
                 issueRecordingState,
                 iActivityManager,
                 notificationManager,
@@ -85,7 +85,7 @@
         bgExecutor.runAllReady()
 
         Truth.assertThat(issueRecordingState.isRecording).isTrue()
-        verify(traceurMessageSender).startTracing(any<TraceConfig>())
+        verify(traceurConnection).startTracing(any<TraceConfig>())
     }
 
     @Test
@@ -94,12 +94,12 @@
         bgExecutor.runAllReady()
 
         Truth.assertThat(issueRecordingState.isRecording).isFalse()
-        verify(traceurMessageSender).stopTracing()
+        verify(traceurConnection).stopTracing()
     }
 
     @Test
     fun cancelsNotification_afterReceivingShareCommand() {
-        underTest.share(0, null, mContext)
+        underTest.share(0, null)
         bgExecutor.runAllReady()
 
         verify(notificationManager).cancelAsUser(isNull(), anyInt(), any<UserHandle>())
@@ -110,7 +110,7 @@
         issueRecordingState.takeBugreport = true
         val uri = mock<Uri>()
 
-        underTest.share(0, uri, mContext)
+        underTest.share(0, uri)
         bgExecutor.runAllReady()
 
         verify(iActivityManager).requestBugReportWithExtraAttachment(uri)
@@ -121,17 +121,17 @@
         issueRecordingState.takeBugreport = false
         val uri = mock<Uri>()
 
-        underTest.share(0, uri, mContext)
+        underTest.share(0, uri)
         bgExecutor.runAllReady()
 
-        verify(traceurMessageSender).shareTraces(mContext, uri)
+        verify(traceurConnection).shareTraces(uri)
     }
 
     @Test
     fun closesShade_afterReceivingShareCommand() {
         val uri = mock<Uri>()
 
-        underTest.share(0, uri, mContext)
+        underTest.share(0, uri)
         bgExecutor.runAllReady()
 
         verify(panelInteractor).collapsePanels()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 8d84c3e..9639735 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -78,7 +78,6 @@
     @Mock private lateinit var sysuiState: SysUiState
     @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock private lateinit var traceurMessageSender: TraceurMessageSender
     private val systemClock = FakeSystemClock()
     private val bgExecutor = FakeExecutor(systemClock)
     private val mainExecutor = FakeExecutor(systemClock)
@@ -104,7 +103,7 @@
                     systemUIDialogManager,
                     sysuiState,
                     broadcastDispatcher,
-                    mDialogTransitionAnimator
+                    mDialogTransitionAnimator,
                 )
             )
 
@@ -120,7 +119,6 @@
                     mediaProjectionMetricsLogger,
                     screenCaptureDisabledDialogDelegate,
                     state,
-                    traceurMessageSender
                 ) {
                     latch.countDown()
                 }
@@ -166,7 +164,7 @@
         verify(mediaProjectionMetricsLogger, never())
             .notifyProjectionInitiated(
                 anyInt(),
-                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
             )
         assertThat(screenRecordSwitch.isChecked).isFalse()
     }
@@ -188,7 +186,7 @@
         verify(mediaProjectionMetricsLogger)
             .notifyProjectionInitiated(
                 anyInt(),
-                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
             )
         verify(factory, times(2)).create(any(SystemUIDialog.Delegate::class.java))
     }
@@ -208,7 +206,7 @@
         verify(mediaProjectionMetricsLogger)
             .notifyProjectionInitiated(
                 anyInt(),
-                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
             )
         verify(factory, never()).create()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt
new file mode 100644
index 0000000..d90cca9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.os.IBinder
+import android.os.Looper
+import android.os.Messenger
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserContextProvider
+import com.android.traceur.PresetTraceConfigs
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class TraceurConnectionTest : SysuiTestCase() {
+
+    @Mock private lateinit var userContextProvider: UserContextProvider
+
+    private lateinit var underTest: TraceurConnection
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(userContextProvider.userContext).thenReturn(mContext)
+        underTest = TraceurConnection.Provider(userContextProvider, Looper.getMainLooper()).create()
+    }
+
+    @Test
+    fun onBoundRunnables_areRun_whenServiceIsBound() {
+        val latch = CountDownLatch(1)
+        underTest.onBound.add { latch.countDown() }
+
+        underTest.onServiceConnected(
+            InstrumentationRegistry.getInstrumentation().componentName,
+            mock(IBinder::class.java),
+        )
+
+        latch.await()
+    }
+
+    @Test
+    fun startTracing_sendsMsg_toStartTracing() {
+        underTest.binder = mock(Messenger::class.java)
+
+        underTest.startTracing(PresetTraceConfigs.getThermalConfig())
+
+        verify(underTest.binder)!!.send(any())
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt
new file mode 100644
index 0000000..f671bf4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.Context
+import android.content.Intent
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserContextProvider
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class UserAwareConnectionTest : SysuiTestCase() {
+
+    @Mock private lateinit var userContextProvider: UserContextProvider
+    @Mock private lateinit var mockContext: Context
+
+    private lateinit var underTest: UserAwareConnection
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(userContextProvider.userContext).thenReturn(mockContext)
+        whenever(mockContext.bindService(any(), any(), anyInt())).thenReturn(true)
+        underTest = UserAwareConnection(userContextProvider, Intent())
+    }
+
+    @Test
+    fun doBindService_requestToBindToTheService_viaTheCorrectUserContext() {
+        underTest.doBind()
+
+        verify(userContextProvider).userContext
+    }
+
+    @Test
+    fun doBindService_DoesntRequestToBindToTheService_IfAlreadyRequested() {
+        underTest.doBind()
+        underTest.doBind()
+        underTest.doBind()
+
+        verify(userContextProvider, times(1)).userContext
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 3b5d5a8..b19b2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -114,7 +114,7 @@
                 faceAuthenticationLogger.bouncerVisibilityChanged()
                 runFaceAuth(
                     FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
-                    fallbackToDetect = false
+                    fallbackToDetect = false,
                 )
             }
             .launchIn(applicationScope)
@@ -125,7 +125,7 @@
                 faceAuthenticationLogger.alternateBouncerVisibilityChanged()
                 runFaceAuth(
                     FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
-                    fallbackToDetect = false
+                    fallbackToDetect = false,
                 )
             }
             .launchIn(applicationScope)
@@ -153,7 +153,7 @@
                     it.lastWakeReason.powerManagerWakeReason
                 runFaceAuth(
                     FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
-                    fallbackToDetect = true
+                    fallbackToDetect = true,
                 )
             }
             .launchIn(applicationScope)
@@ -193,13 +193,16 @@
             .map { (_, curr) -> curr.userInfo.id }
             .sample(isBouncerVisible, ::Pair)
             .onEach { (userId, isBouncerCurrentlyVisible) ->
+                if (!isFaceAuthEnabledAndEnrolled()) {
+                    return@onEach
+                }
                 resetLockedOutState(userId)
                 yield()
                 runFaceAuth(
                     FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
                     // Fallback to detection if bouncer is not showing so that we can detect a
                     // face and then show the bouncer to the user if face auth can't run
-                    fallbackToDetect = !isBouncerCurrentlyVisible
+                    fallbackToDetect = !isBouncerCurrentlyVisible,
                 )
             }
             .launchIn(applicationScope)
@@ -210,7 +213,7 @@
                     repository.cancel()
                     runFaceAuth(
                         FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED,
-                        fallbackToDetect = true
+                        fallbackToDetect = true,
                     )
                 }
             }
@@ -321,7 +324,7 @@
             faceAuthenticationStatusOverride.value =
                 ErrorFaceAuthenticationStatus(
                     BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
-                    context.resources.getString(R.string.keyguard_face_unlock_unavailable)
+                    context.resources.getString(R.string.keyguard_face_unlock_unavailable),
                 )
         } else {
             faceAuthenticationStatusOverride.value = null
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
index 0e2d9b6..43e39cf 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -30,7 +30,7 @@
 import java.time.Clock
 import javax.inject.Inject
 import kotlin.time.Duration
-import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.days
 import kotlin.time.DurationUnit
 import kotlin.time.toDuration
 import kotlinx.coroutines.CoroutineScope
@@ -64,7 +64,7 @@
             get() =
                 SystemProperties.getLong(
                         "persist.contextual_edu.initial_delay_sec",
-                        /* defaultValue= */ 72.hours.inWholeSeconds
+                        /* defaultValue= */ 7.days.inWholeSeconds,
                     )
                     .toDuration(DurationUnit.SECONDS)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index ca1a800..68244d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -60,6 +60,7 @@
     primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
     primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
     primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+    lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
 ) {
     val color: Flow<Int> =
         deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
@@ -103,7 +104,9 @@
                         offToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         primaryBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
-                        primaryBouncerToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+                        primaryBouncerToLockscreenTransitionViewModel
+                            .deviceEntryBackgroundViewAlpha,
+                        lockscreenToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
                     )
                     .merge()
                     .onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index d3eefca..7abf35d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -55,16 +55,16 @@
             onCancel = { 1f },
         )
 
+    val deviceEntryBackgroundViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0f)
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
             isUdfpsEnrolledAndEnabled ->
             if (isUdfpsEnrolledAndEnabled) {
                 transitionAnimation.immediatelyTransitionTo(1f)
             } else {
-                transitionAnimation.sharedFlow(
-                    duration = 250.milliseconds,
-                    onStep = { 1f - it },
-                )
+                transitionAnimation.sharedFlow(duration = 250.milliseconds, onStep = { 1f - it })
             }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index d89e73d..fb406d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -45,10 +45,11 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent
 import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent
+import com.android.systemui.recordissue.IssueRecordingServiceConnection
 import com.android.systemui.recordissue.IssueRecordingState
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC
-import com.android.systemui.recordissue.TraceurMessageSender
+import com.android.systemui.recordissue.TraceurConnection
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.screenrecord.RecordingService
@@ -66,7 +67,7 @@
 constructor(
     host: QSHost,
     uiEventLogger: QsEventLogger,
-    @Background backgroundLooper: Looper,
+    @Background private val backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
     metricsLogger: MetricsLogger,
@@ -78,7 +79,8 @@
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val panelInteractor: PanelInteractor,
     private val userContextProvider: UserContextProvider,
-    private val traceurMessageSender: TraceurMessageSender,
+    irsConnProvider: IssueRecordingServiceConnection.Provider,
+    traceurConnProvider: TraceurConnection.Provider,
     @Background private val bgExecutor: Executor,
     private val issueRecordingState: IssueRecordingState,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
@@ -93,11 +95,20 @@
         metricsLogger,
         statusBarStateController,
         activityStarter,
-        qsLogger
+        qsLogger,
     ) {
 
     private val onRecordingChangeListener = Runnable { refreshState() }
 
+    private val irsConnection: IssueRecordingServiceConnection = irsConnProvider.create()
+    private val traceurConnection =
+        traceurConnProvider.create().apply {
+            onBound.add {
+                getTags(issueRecordingState)
+                doUnBind()
+            }
+        }
+
     override fun handleSetListening(listening: Boolean) {
         super.handleSetListening(listening)
         if (listening) {
@@ -109,7 +120,7 @@
 
     override fun handleDestroy() {
         super.handleDestroy()
-        bgExecutor.execute { traceurMessageSender.unbindFromTraceur(mContext) }
+        bgExecutor.execute { irsConnection.doUnBind() }
     }
 
     override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label)
@@ -142,7 +153,7 @@
             DELAY_MS,
             INTERVAL_MS,
             pendingServiceIntent(getStartIntent(userContextProvider.userContext)),
-            pendingServiceIntent(getStopIntent(userContextProvider.userContext))
+            pendingServiceIntent(getStopIntent(userContextProvider.userContext)),
         )
 
     private fun stopIssueRecordingService() =
@@ -154,10 +165,19 @@
             userContextProvider.userContext,
             RecordingService.REQUEST_CODE,
             action,
-            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
         )
 
     private fun showPrompt(expandable: Expandable?) {
+        bgExecutor.execute {
+            // We only want to get the tags once per session, as this is not likely to change, if at
+            // all on a month to month basis. Using onBound's size is a way to verify if the tag
+            // retrieval has already happened or not.
+            if (traceurConnection.onBound.isNotEmpty()) {
+                traceurConnection.doBind()
+            }
+            irsConnection.doBind()
+        }
         val dialog: AlertDialog =
             delegateFactory
                 .create {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4d2bc91..3f875bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -23,9 +23,12 @@
 import android.content.res.Resources
 import android.net.Uri
 import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
 import android.util.Log
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.LongRunning
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
@@ -42,6 +45,7 @@
 @Inject
 constructor(
     controller: RecordingController,
+    @Background private val bgLooper: Looper,
     @LongRunning private val bgExecutor: Executor,
     @Main handler: Handler,
     uiEventLogger: UiEventLogger,
@@ -50,8 +54,8 @@
     keyguardDismissUtil: KeyguardDismissUtil,
     dialogTransitionAnimator: DialogTransitionAnimator,
     panelInteractor: PanelInteractor,
-    traceurMessageSender: TraceurMessageSender,
     private val issueRecordingState: IssueRecordingState,
+    traceurConnectionProvider: TraceurConnection.Provider,
     iActivityManager: IActivityManager,
 ) :
     RecordingService(
@@ -64,18 +68,37 @@
         keyguardDismissUtil,
     ) {
 
+    private val traceurConnection: TraceurConnection = traceurConnectionProvider.create()
+
     private val session =
         IssueRecordingServiceSession(
             bgExecutor,
             dialogTransitionAnimator,
             panelInteractor,
-            traceurMessageSender,
+            traceurConnection,
             issueRecordingState,
             iActivityManager,
             notificationManager,
             userContextProvider,
         )
 
+    /**
+     * It is necessary to bind to IssueRecordingService from the Record Issue Tile because there are
+     * instances where this service is not created in the same user profile as the record issue tile
+     * aka, headless system user mode. In those instances, the TraceurConnection will be considered
+     * a leak in between notification actions unless the tile is bound to this service to keep it
+     * alive.
+     */
+    override fun onBind(intent: Intent): IBinder? {
+        traceurConnection.doBind()
+        return super.onBind(intent)
+    }
+
+    override fun onUnbind(intent: Intent?): Boolean {
+        traceurConnection.doUnBind()
+        return super.onUnbind(intent)
+    }
+
     override fun getTag(): String = TAG
 
     override fun getChannelId(): String = CHANNEL_ID
@@ -99,7 +122,6 @@
                 session.share(
                     intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId),
                     intent.getParcelableExtra(EXTRA_PATH, Uri::class.java),
-                    this,
                 )
                 // Unlike all other actions, action_share has different behavior for the screen
                 // recording qs tile than it does for the record issue qs tile. Return sticky to
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceConnection.kt
new file mode 100644
index 0000000..85a5805
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceConnection.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.Intent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserContextProvider
+import com.android.traceur.MessageConstants.SYSTEM_UI_PACKAGE_NAME
+import javax.inject.Inject
+
+/**
+ * It is necessary to bind to IssueRecordingService from the Record Issue Tile because there are
+ * instances where this service is not created in the same user profile as the record issue tile
+ * aka, headless system user mode. In those instances, the TraceurConnection will be considered a
+ * leak in between notification actions unless the tile is bound to this service to keep it alive.
+ */
+class IssueRecordingServiceConnection(userContextProvider: UserContextProvider) :
+    UserAwareConnection(
+        userContextProvider,
+        Intent().setClassName(SYSTEM_UI_PACKAGE_NAME, IssueRecordingService::class.java.name),
+    ) {
+    @SysUISingleton
+    class Provider @Inject constructor(private val userContextProvider: UserContextProvider) {
+        fun create() = IssueRecordingServiceConnection(userContextProvider)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
index e4d3e6c..ad9b4fe 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
@@ -19,7 +19,6 @@
 import android.app.IActivityManager
 import android.app.NotificationManager
 import android.content.ContentResolver
-import android.content.Context
 import android.net.Uri
 import android.os.UserHandle
 import android.provider.Settings
@@ -42,7 +41,7 @@
     private val bgExecutor: Executor,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val panelInteractor: PanelInteractor,
-    private val traceurMessageSender: TraceurMessageSender,
+    private val traceurConnection: TraceurConnection,
     private val issueRecordingState: IssueRecordingState,
     private val iActivityManager: IActivityManager,
     private val notificationManager: NotificationManager,
@@ -50,7 +49,7 @@
 ) {
 
     fun start() {
-        bgExecutor.execute { traceurMessageSender.startTracing(issueRecordingState.traceConfig) }
+        bgExecutor.execute { traceurConnection.startTracing(issueRecordingState.traceConfig) }
         issueRecordingState.isRecording = true
     }
 
@@ -59,12 +58,12 @@
             if (issueRecordingState.traceConfig.longTrace) {
                 Settings.Global.putInt(contentResolver, NOTIFY_SESSION_ENDED_SETTING, DISABLED)
             }
-            traceurMessageSender.stopTracing()
+            traceurConnection.stopTracing()
         }
         issueRecordingState.isRecording = false
     }
 
-    fun share(notificationId: Int, screenRecording: Uri?, context: Context) {
+    fun share(notificationId: Int, screenRecording: Uri?) {
         bgExecutor.execute {
             notificationManager.cancelAsUser(
                 null,
@@ -75,7 +74,7 @@
             if (issueRecordingState.takeBugreport) {
                 iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
             } else {
-                traceurMessageSender.shareTraces(context, screenRecording)
+                traceurConnection.shareTraces(screenRecording)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index ed67e64..6758c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -64,7 +64,6 @@
     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
     private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
     private val state: IssueRecordingState,
-    private val traceurMessageSender: TraceurMessageSender,
     @Assisted private val onStarted: Runnable,
 ) : SystemUIDialog.Delegate {
 
@@ -87,10 +86,6 @@
             setNegativeButton(R.string.cancel) { _, _ -> }
             setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
         }
-        bgExecutor.execute {
-            traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags(state) }
-            traceurMessageSender.bindToTraceur(dialog.context)
-        }
     }
 
     override fun createDialog(): SystemUIDialog = factory.create(this)
@@ -151,7 +146,7 @@
 
         mediaProjectionMetricsLogger.notifyProjectionInitiated(
             userTracker.userId,
-            SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+            SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER,
         )
 
         if (!state.hasUserApprovedScreenRecording) {
@@ -189,7 +184,7 @@
                         CustomTraceSettingsDialogDelegate(
                                 factory,
                                 state.customTraceState,
-                                state.tagTitles
+                                state.tagTitles,
                             ) {
                                 onMenuItemClickListener.onMenuItemClick(it)
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt
new file mode 100644
index 0000000..81529b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.ComponentName
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.util.Log
+import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
+import com.android.systemui.settings.UserContextProvider
+import com.android.traceur.FileSender
+import com.android.traceur.MessageConstants
+import com.android.traceur.MessageConstants.TRACING_APP_ACTIVITY
+import com.android.traceur.MessageConstants.TRACING_APP_PACKAGE_NAME
+import com.android.traceur.TraceConfig
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+private const val TAG = "TraceurConnection"
+
+class TraceurConnection
+private constructor(userContextProvider: UserContextProvider, private val bgLooper: Looper) :
+    UserAwareConnection(
+        userContextProvider,
+        Intent().setClassName(TRACING_APP_PACKAGE_NAME, TRACING_APP_ACTIVITY),
+    ) {
+
+    @SysUISingleton
+    class Provider
+    @Inject
+    constructor(
+        private val userContextProvider: UserContextProvider,
+        @Background private val bgLooper: Looper,
+    ) {
+        fun create() = TraceurConnection(userContextProvider, bgLooper)
+    }
+
+    val onBound: MutableList<Runnable> = CopyOnWriteArrayList(mutableListOf())
+
+    override fun onServiceConnected(className: ComponentName, service: IBinder) {
+        super.onServiceConnected(className, service)
+        onBound.forEach(Runnable::run)
+        onBound.clear()
+    }
+
+    @WorkerThread
+    fun startTracing(traceType: TraceConfig) {
+        val data =
+            Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
+        sendMessage(MessageConstants.START_WHAT, data)
+    }
+
+    @WorkerThread fun stopTracing() = sendMessage(MessageConstants.STOP_WHAT)
+
+    @WorkerThread
+    fun shareTraces(screenRecord: Uri?) {
+        val replyHandler = Messenger(ShareFilesHandler(screenRecord, userContextProvider, bgLooper))
+        sendMessage(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
+    }
+
+    @WorkerThread
+    fun getTags(state: IssueRecordingState) =
+        sendMessage(MessageConstants.TAGS_WHAT, replyTo = Messenger(TagsHandler(bgLooper, state)))
+
+    @WorkerThread
+    private fun sendMessage(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) =
+        try {
+            val msg =
+                Message.obtain().apply {
+                    this.what = what
+                    this.data = data
+                    this.replyTo = replyTo
+                }
+            binder?.send(msg) ?: onBound.add { binder!!.send(msg) }
+        } catch (e: Exception) {
+            Log.e(TAG, "failed to notify Traceur", e)
+        }
+}
+
+private class ShareFilesHandler(
+    private val screenRecord: Uri?,
+    private val userContextProvider: UserContextProvider,
+    looper: Looper,
+) : Handler(looper) {
+
+    override fun handleMessage(msg: Message) {
+        if (MessageConstants.SHARE_WHAT == msg.what) {
+            shareTraces(
+                msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java),
+                msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java),
+            )
+        } else {
+            throw IllegalArgumentException("received unknown msg.what: " + msg.what)
+        }
+    }
+
+    private fun shareTraces(perfetto: Uri?, winscope: Uri?) {
+        val uris: ArrayList<Uri> =
+            ArrayList<Uri>().apply {
+                perfetto?.let { add(it) }
+                winscope?.let { add(it) }
+                screenRecord?.let { add(it) }
+            }
+        val fileSharingIntent =
+            FileSender.buildSendIntent(userContextProvider.userContext, uris)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+        userContextProvider.userContext.startActivity(fileSharingIntent)
+    }
+}
+
+private class TagsHandler(looper: Looper, private val state: IssueRecordingState) :
+    Handler(looper) {
+
+    override fun handleMessage(msg: Message) {
+        if (MessageConstants.TAGS_WHAT == msg.what) {
+            val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS)
+            val values = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
+            if (keys == null || values == null) {
+                throw IllegalArgumentException(
+                    "Neither keys: $keys, nor values: $values can be null"
+                )
+            }
+            state.tagTitles =
+                keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet()
+        } else {
+            throw IllegalArgumentException("received unknown msg.what: " + msg.what)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
deleted file mode 100644
index 8bfd14a..0000000
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recordissue
-
-import android.annotation.SuppressLint
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.content.pm.PackageManager
-import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.os.IBinder
-import android.os.Looper
-import android.os.Message
-import android.os.Messenger
-import android.util.Log
-import androidx.annotation.WorkerThread
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
-import com.android.traceur.FileSender
-import com.android.traceur.MessageConstants
-import com.android.traceur.TraceConfig
-import javax.inject.Inject
-
-private const val TAG = "TraceurMessageSender"
-
-@SysUISingleton
-class TraceurMessageSender @Inject constructor(@Background private val backgroundLooper: Looper) {
-    private var binder: Messenger? = null
-    private var isBound: Boolean = false
-
-    val onBoundToTraceur = mutableListOf<Runnable>()
-
-    private val traceurConnection =
-        object : ServiceConnection {
-            override fun onServiceConnected(className: ComponentName, service: IBinder) {
-                binder = Messenger(service)
-                isBound = true
-                onBoundToTraceur.forEach(Runnable::run)
-                onBoundToTraceur.clear()
-            }
-
-            override fun onServiceDisconnected(className: ComponentName) {
-                binder = null
-                isBound = false
-            }
-        }
-
-    @SuppressLint("WrongConstant")
-    @WorkerThread
-    fun bindToTraceur(context: Context) {
-        if (isBound) {
-            // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is
-            // initialized before this happens though, so binding is placed at a later time, during
-            // normal operations that can be repeated. This check avoids calling "bindService" 2x+
-            return
-        }
-        try {
-            val info =
-                context.packageManager.getPackageInfo(
-                    MessageConstants.TRACING_APP_PACKAGE_NAME,
-                    PackageManager.MATCH_SYSTEM_ONLY
-                )
-            val intent =
-                Intent().setClassName(info.packageName, MessageConstants.TRACING_APP_ACTIVITY)
-            val flags =
-                Context.BIND_AUTO_CREATE or
-                    Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or
-                    Context.BIND_WAIVE_PRIORITY
-            context.bindService(intent, traceurConnection, flags)
-        } catch (e: Exception) {
-            Log.e(TAG, "failed to bind to Traceur's service", e)
-        }
-    }
-
-    @WorkerThread
-    fun unbindFromTraceur(context: Context) {
-        if (isBound) {
-            context.unbindService(traceurConnection)
-        }
-    }
-
-    @WorkerThread
-    fun startTracing(traceType: TraceConfig) {
-        val data =
-            Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
-        notifyTraceur(MessageConstants.START_WHAT, data)
-    }
-
-    @WorkerThread fun stopTracing() = notifyTraceur(MessageConstants.STOP_WHAT)
-
-    @WorkerThread
-    fun shareTraces(context: Context, screenRecord: Uri?) {
-        val replyHandler = Messenger(ShareFilesHandler(context, screenRecord, backgroundLooper))
-        notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
-    }
-
-    @WorkerThread
-    fun getTags(state: IssueRecordingState) {
-        val replyHandler = Messenger(TagsHandler(backgroundLooper, state))
-        notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler)
-    }
-
-    @WorkerThread
-    private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) {
-        try {
-            binder!!.send(
-                Message.obtain().apply {
-                    this.what = what
-                    this.data = data
-                    this.replyTo = replyTo
-                }
-            )
-        } catch (e: Exception) {
-            Log.e(TAG, "failed to notify Traceur", e)
-        }
-    }
-
-    private class ShareFilesHandler(
-        private val context: Context,
-        private val screenRecord: Uri?,
-        looper: Looper,
-    ) : Handler(looper) {
-
-        override fun handleMessage(msg: Message) {
-            if (MessageConstants.SHARE_WHAT == msg.what) {
-                shareTraces(
-                    msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java),
-                    msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java)
-                )
-            } else {
-                throw IllegalArgumentException("received unknown msg.what: " + msg.what)
-            }
-        }
-
-        private fun shareTraces(perfetto: Uri?, winscope: Uri?) {
-            val uris: List<Uri> =
-                mutableListOf<Uri>().apply {
-                    perfetto?.let { add(it) }
-                    winscope?.let { add(it) }
-                    screenRecord?.let { add(it) }
-                }
-            val fileSharingIntent =
-                FileSender.buildSendIntent(context, uris)
-                    .addFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
-                    )
-            context.startActivity(fileSharingIntent)
-        }
-    }
-
-    private class TagsHandler(looper: Looper, private val state: IssueRecordingState) :
-        Handler(looper) {
-
-        override fun handleMessage(msg: Message) {
-            if (MessageConstants.TAGS_WHAT == msg.what) {
-                val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS)
-                val values =
-                    msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
-                if (keys == null || values == null) {
-                    throw IllegalArgumentException(
-                        "Neither keys: $keys, nor values: $values can be null"
-                    )
-                }
-                state.tagTitles =
-                    keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet()
-            } else {
-                throw IllegalArgumentException("received unknown msg.what: " + msg.what)
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
new file mode 100644
index 0000000..6aaa27d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.Messenger
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.systemui.settings.UserContextProvider
+
+private const val TAG = "UserAwareConnection"
+private const val BIND_FLAGS =
+    Context.BIND_AUTO_CREATE or
+        Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or
+        Context.BIND_WAIVE_PRIORITY
+
+/** ServiceConnection class that can be used to keep an IntentService alive. */
+open class UserAwareConnection(
+    protected val userContextProvider: UserContextProvider,
+    private val intent: Intent,
+) : ServiceConnection {
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) var binder: Messenger? = null
+    private var shouldUnBind = false
+
+    override fun onServiceConnected(className: ComponentName, service: IBinder) {
+        binder = Messenger(service)
+    }
+
+    override fun onServiceDisconnected(className: ComponentName) {
+        binder = null
+    }
+
+    @SuppressLint("WrongConstant")
+    @WorkerThread
+    fun doBind() {
+        if (shouldUnBind) {
+            // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is
+            // initialized before this happens though, so binding is placed at a later time, during
+            // normal operations that can be repeated. This check avoids calling "bindService" 2x+
+            return
+        }
+        try {
+            shouldUnBind = userContextProvider.userContext.bindService(intent, this, BIND_FLAGS)
+        } catch (e: Exception) {
+            Log.e(TAG, "failed to bind to the service", e)
+        }
+    }
+
+    @WorkerThread
+    fun doUnBind() {
+        if (shouldUnBind) {
+            userContextProvider.userContext.unbindService(this)
+            shouldUnBind = false
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index 0806be8..a762d84 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -97,12 +97,13 @@
     private val window: ScreenshotWindow
     private val actionExecutor: ActionExecutor
     private val copyBroadcastReceiver: BroadcastReceiver
+    private val currentRequestCallbacks: MutableList<TakeScreenshotService.RequestCallback> =
+        mutableListOf()
 
     private var screenshotSoundController: ScreenshotSoundController? = null
     private var screenBitmap: Bitmap? = null
     private var screenshotTakenInPortrait = false
     private var screenshotAnimation: Animator? = null
-    private var currentRequestCallback: TakeScreenshotService.RequestCallback? = null
     private var packageName = ""
 
     /** Tracks config changes that require re-creating UI */
@@ -169,8 +170,8 @@
         requestCallback: TakeScreenshotService.RequestCallback,
     ) {
         Assert.isMainThread()
+        screenshotHandler.resetTimeout()
 
-        currentRequestCallback = requestCallback
         if (screenshot.type == TAKE_SCREENSHOT_FULLSCREEN && screenshot.bitmap == null) {
             val bounds = fullScreenRect
             screenshot.bitmap = imageCapture.captureDisplay(display.displayId, bounds)
@@ -181,7 +182,7 @@
         if (currentBitmap == null) {
             Log.e(TAG, "handleScreenshot: Screenshot bitmap was null")
             notificationController.notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
-            currentRequestCallback?.reportError()
+            requestCallback.reportError()
             return
         }
 
@@ -194,8 +195,10 @@
             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
             // and sharing shouldn't be exposed to the user.
             saveScreenshotAndToast(screenshot, finisher)
+            requestCallback.onFinish()
             return
         }
+        currentRequestCallbacks.add(requestCallback)
 
         broadcastSender.sendBroadcast(
             Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
@@ -495,8 +498,8 @@
         Log.d(TAG, "finishDismiss")
         actionsController.endScreenshotSession()
         scrollCaptureExecutor.close()
-        currentRequestCallback?.onFinish()
-        currentRequestCallback = null
+        currentRequestCallbacks.forEach { it.onFinish() }
+        currentRequestCallbacks.clear()
         viewProxy.reset()
         removeWindow()
         screenshotHandler.cancelTimeout()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b466bf0..b171e87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1160,11 +1160,13 @@
 
     @Override
     public void addHeadsUpHeightChangedListener(@NonNull Runnable runnable) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mHeadsUpHeightChangedListeners.addIfAbsent(runnable);
     }
 
     @Override
     public void removeHeadsUpHeightChangedListener(@NonNull Runnable runnable) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mHeadsUpHeightChangedListeners.remove(runnable);
     }
 
@@ -1240,11 +1242,13 @@
 
     @Override
     public void setScrolledToTop(boolean scrolledToTop) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mScrollViewFields.setScrolledToTop(scrolledToTop);
     }
 
     @Override
     public void setStackTop(float stackTop) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         if (mAmbientState.getStackTop() != stackTop) {
             mAmbientState.setStackTop(stackTop);
             onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
@@ -1253,51 +1257,54 @@
 
     @Override
     public void setStackCutoff(float stackCutoff) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mAmbientState.setStackCutoff(stackCutoff);
     }
 
     @Override
     public void setHeadsUpTop(float headsUpTop) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mAmbientState.setHeadsUpTop(headsUpTop);
         requestChildrenUpdate();
     }
 
     @Override
     public void setHeadsUpBottom(float headsUpBottom) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mAmbientState.setHeadsUpBottom(headsUpBottom);
         mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
     }
 
     @Override
     public void closeGutsOnSceneTouch() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mController.closeControlsDueToOutsideTouch();
     }
 
     @Override
     public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mScrollViewFields.setSyntheticScrollConsumer(consumer);
     }
 
     @Override
     public void setCurrentGestureOverscrollConsumer(@Nullable Consumer<Boolean> consumer) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mScrollViewFields.setCurrentGestureOverscrollConsumer(consumer);
     }
 
     @Override
     public void setCurrentGestureInGutsConsumer(@Nullable Consumer<Boolean> consumer) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mScrollViewFields.setCurrentGestureInGutsConsumer(consumer);
     }
 
     @Override
     public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer);
     }
 
-    @Override
-    public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
-        mScrollViewFields.setHeadsUpHeightConsumer(consumer);
-    }
-
     /**
      * @param listener to be notified after the location of Notification children might have
      *                 changed.
@@ -2621,11 +2628,13 @@
 
     @Override
     public int getTopHeadsUpHeight() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0;
         return getTopHeadsUpIntrinsicHeight();
     }
 
     @Override
     public int getHeadsUpInset() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0;
         return mHeadsUpInset;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index c08ed61..f6e8b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -86,9 +86,6 @@
     fun sendRemoteInputRowBottomBound(bottomY: Float?) =
         remoteInputRowBottomBoundConsumer?.accept(bottomY)
 
-    /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
-    fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
-
     fun dump(pw: IndentingPrintWriter) {
         pw.printSection("StackViewStates") {
             pw.println("scrimClippingShape", scrimClippingShape)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index dbe81c1..6ad9f01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -77,9 +77,6 @@
     /** Set a consumer for current remote input notification row bottom bound events */
     fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?)
 
-    /** Set a consumer for heads up height changed events */
-    fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
-
     /** sets that scrolling is allowed */
     fun setScrollingEnabled(enabled: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt
deleted file mode 100644
index 4e5ecfe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.CallbackController
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-
-/**
- * Publishes updates to the status bar's margins.
- *
- * While the status bar view consumes the entire width of the device, the status bar
- * contents are laid out with margins for rounded corners, padding from the absolute
- * edges, and potentially display cutouts in the corner.
- */
-@SysUISingleton
-class StatusBarLocationPublisher @Inject constructor()
-: CallbackController<StatusBarMarginUpdatedListener> {
-    private val listeners = mutableSetOf<WeakReference<StatusBarMarginUpdatedListener>>()
-
-    var marginLeft: Int = 0
-        private set
-    var marginRight: Int = 0
-        private set
-
-    override fun addCallback(listener: StatusBarMarginUpdatedListener) {
-        listeners.add(WeakReference(listener))
-    }
-
-    override fun removeCallback(listener: StatusBarMarginUpdatedListener) {
-        var toRemove: WeakReference<StatusBarMarginUpdatedListener>? = null
-        for (l in listeners) {
-            if (l.get() == listener) {
-                toRemove = l
-            }
-        }
-
-        if (toRemove != null) {
-            listeners.remove(toRemove)
-        }
-    }
-
-    fun updateStatusBarMargin(left: Int, right: Int) {
-        marginLeft = left
-        marginRight = right
-
-        notifyListeners()
-    }
-
-    private fun notifyListeners() {
-        var listenerList: List<WeakReference<StatusBarMarginUpdatedListener>>
-        synchronized(this) {
-            listenerList = listeners.toList()
-        }
-
-        listenerList.forEach { wrapper ->
-            if (wrapper.get() == null) {
-                listeners.remove(wrapper)
-            }
-
-            wrapper.get()?.onStatusBarMarginUpdated(marginLeft, marginRight)
-        }
-    }
-}
-
-interface StatusBarMarginUpdatedListener {
-    fun onStatusBarMarginUpdated(marginLeft: Int, marginRight: Int)
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index a8b4728..c258095 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -65,7 +65,6 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
@@ -140,7 +139,6 @@
     private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
-    private final StatusBarLocationPublisher mLocationPublisher;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
     private final CarrierConfigTracker mCarrierConfigTracker;
@@ -243,7 +241,6 @@
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
             OngoingCallController ongoingCallController,
             SystemStatusAnimationScheduler animationScheduler,
-            StatusBarLocationPublisher locationPublisher,
             ShadeExpansionStateManager shadeExpansionStateManager,
             StatusBarIconController statusBarIconController,
             DarkIconManager.Factory darkIconManagerFactory,
@@ -267,7 +264,6 @@
         mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
-        mLocationPublisher = locationPublisher;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mStatusBarIconController = statusBarIconController;
         mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
@@ -349,9 +345,6 @@
         }
 
         mStatusBar = (PhoneStatusBarView) view;
-        View contents = mStatusBar.findViewById(R.id.status_bar_contents);
-        contents.addOnLayoutChangeListener(mStatusBarLayoutListener);
-        updateStatusBarLocation(contents.getLeft(), contents.getRight());
         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
@@ -977,13 +970,6 @@
         }, /*isAnimationRunning*/ false);
     }
 
-    private void updateStatusBarLocation(int left, int right) {
-        int leftMargin = left - mStatusBar.getLeft();
-        int rightMargin = mStatusBar.getRight() - right;
-
-        mLocationPublisher.updateStatusBarMargin(leftMargin, rightMargin);
-    }
-
     private final ContentObserver mVolumeSettingObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean selfChange) {
@@ -991,14 +977,6 @@
         }
     };
 
-    // Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot
-    private View.OnLayoutChangeListener mStatusBarLayoutListener =
-            (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                if (left != oldLeft || right != oldRight) {
-                    updateStatusBarLocation(left, right);
-                }
-            };
-
     @Override
     public void dump(PrintWriter printWriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */"  ");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index ca518f9..c7da03d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles
 
 import android.os.Handler
+import android.os.Looper
 import android.service.quicksettings.Tile
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -31,9 +32,10 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.recordissue.IssueRecordingServiceConnection
 import com.android.systemui.recordissue.IssueRecordingState
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
-import com.android.systemui.recordissue.TraceurMessageSender
+import com.android.systemui.recordissue.TraceurConnection
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.settings.UserContextProvider
@@ -75,13 +77,14 @@
     @Mock private lateinit var panelInteractor: PanelInteractor
     @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var issueRecordingState: IssueRecordingState
-    @Mock private lateinit var traceurMessageSender: TraceurMessageSender
     @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
     @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
     @Mock private lateinit var dialog: SystemUIDialog
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: RecordIssueTile
+    private lateinit var irsConnProvider: IssueRecordingServiceConnection.Provider
+    private lateinit var traceurConnProvider: TraceurConnection.Provider
 
     @Before
     fun setUp() {
@@ -90,6 +93,10 @@
         whenever(delegateFactory.create(any())).thenReturn(dialogDelegate)
         whenever(dialogDelegate.createDialog()).thenReturn(dialog)
 
+        irsConnProvider = IssueRecordingServiceConnection.Provider(userContextProvider)
+        traceurConnProvider =
+            TraceurConnection.Provider(userContextProvider, Looper.getMainLooper())
+
         testableLooper = TestableLooper.get(this)
         tile =
             RecordIssueTile(
@@ -107,7 +114,8 @@
                 dialogLauncherAnimator,
                 panelInteractor,
                 userContextProvider,
-                traceurMessageSender,
+                irsConnProvider,
+                traceurConnProvider,
                 Executors.newSingleThreadExecutor(),
                 issueRecordingState,
                 delegateFactory,
@@ -169,7 +177,7 @@
             .executeWhenUnlocked(
                 isA(ActivityStarter.OnDismissAction::class.java),
                 eq(false),
-                eq(true)
+                eq(true),
             )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index eb1bcc7..d717fe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -48,7 +48,7 @@
     private val kosmos =
         testKosmos().apply {
             fakeKeyguardTransitionRepository =
-                FakeKeyguardTransitionRepository(initInLockscreen = false)
+                FakeKeyguardTransitionRepository(initInLockscreen = false, testScope = testScope)
         }
     private val testScope = kosmos.testScope
     private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 63a560f..e57e8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -66,7 +66,6 @@
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ui.DarkIconManager;
@@ -98,7 +97,6 @@
     private ShadeExpansionStateManager mShadeExpansionStateManager;
     private OngoingCallController mOngoingCallController;
     private SystemStatusAnimationScheduler mAnimationScheduler;
-    private StatusBarLocationPublisher mLocationPublisher;
     // Set in instantiate()
     private StatusBarIconController mStatusBarIconController;
     private KeyguardStateController mKeyguardStateController;
@@ -1181,7 +1179,6 @@
         setUpDaggerComponent();
         mOngoingCallController = mock(OngoingCallController.class);
         mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
-        mLocationPublisher = mock(StatusBarLocationPublisher.class);
         mStatusBarIconController = mock(StatusBarIconController.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
@@ -1200,7 +1197,6 @@
                 mStatusBarFragmentComponentFactory,
                 mOngoingCallController,
                 mAnimationScheduler,
-                mLocationPublisher,
                 mShadeExpansionStateManager,
                 mStatusBarIconController,
                 mIconManagerFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 4d0e603..70b4f79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -48,13 +48,35 @@
  * with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
  */
 @SysUISingleton
-class FakeKeyguardTransitionRepository(private val initInLockscreen: Boolean = true) :
-    KeyguardTransitionRepository {
+class FakeKeyguardTransitionRepository(
+    private val initInLockscreen: Boolean = true,
+
+    /**
+     * If true, calls to [startTransition] will automatically emit STARTED, RUNNING, and FINISHED
+     * transition steps from/to the given states.
+     *
+     * [startTransition] is what the From*TransitionInteractors call, so this more closely emulates
+     * the behavior of the real KeyguardTransitionRepository, and reduces the work needed to
+     * manually set up the repository state in each test. For example, setting dreaming=true will
+     * automatically cause FromDreamingTransitionInteractor to call startTransition(DREAMING), and
+     * then we'll send STARTED/RUNNING/FINISHED DREAMING TransitionSteps.
+     *
+     * If your test needs to make assertions at specific points between STARTED/FINISHED, or if it's
+     * difficult to set up all of the conditions to make the transition interactors actually call
+     * startTransition, then construct a FakeKeyguardTransitionRepository with this value false.
+     */
+    private val sendTransitionStepsOnStartTransition: Boolean = true,
+    private val testScope: TestScope,
+) : KeyguardTransitionRepository {
+
     private val _transitions =
         MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     override val transitions: SharedFlow<TransitionStep> = _transitions
 
-    @Inject constructor() : this(initInLockscreen = true)
+    @Inject
+    constructor(
+        testScope: TestScope
+    ) : this(initInLockscreen = true, sendTransitionStepsOnStartTransition = true, testScope)
 
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
         MutableStateFlow(
@@ -287,6 +309,11 @@
 
     override suspend fun startTransition(info: TransitionInfo): UUID? {
         _currentTransitionInfo.value = info
+
+        if (sendTransitionStepsOnStartTransition) {
+            sendTransitionSteps(from = info.from, to = info.to, testScope = testScope)
+        }
+
         return if (info.animator == null) UUID.randomUUID() else null
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 3e69e87..e9eea83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -18,9 +18,14 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import org.mockito.Mockito.spy
 
 var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
     Kosmos.Fixture { fakeKeyguardTransitionRepository }
-var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.fakeKeyguardTransitionRepository by
+    Kosmos.Fixture { FakeKeyguardTransitionRepository(testScope = testScope) }
+var Kosmos.fakeKeyguardTransitionRepositorySpy: FakeKeyguardTransitionRepository by
+    Kosmos.Fixture { spy(fakeKeyguardTransitionRepository) }
 var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by
     Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index ef789d1..93a59eb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
@@ -27,7 +27,7 @@
 val Kosmos.fromAodTransitionInteractor by
     Kosmos.Fixture {
         FromAodTransitionInteractor(
-            transitionRepository = fakeKeyguardTransitionRepository,
+            transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index c694114..700d7e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
@@ -29,7 +29,7 @@
 val Kosmos.fromGoneTransitionInteractor by
     Kosmos.Fixture {
         FromGoneTransitionInteractor(
-            transitionRepository = fakeKeyguardTransitionRepository,
+            transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
new file mode 100644
index 0000000..fc4f3a5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntryBackgroundViewModel by Fixture {
+    DeviceEntryBackgroundViewModel(
+        context = applicationContext,
+        deviceEntryIconViewModel = deviceEntryIconViewModel,
+        configurationInteractor = configurationInteractor,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
+        alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
+        alternateBouncerToDozingTransitionViewModel = alternateBouncerToDozingTransitionViewModel,
+        aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+        dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+        dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
+        dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
+        goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+        goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+        goneToLockscreenTransitionViewModel = goneToLockscreenTransitionViewModel,
+        lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
+        occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
+        occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel,
+        occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+        offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
+        primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel,
+        primaryBouncerToDozingTransitionViewModel = primaryBouncerToDozingTransitionViewModel,
+        primaryBouncerToLockscreenTransitionViewModel =
+            primaryBouncerToLockscreenTransitionViewModel,
+        lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
+    )
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0475b94..60dbf3f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -389,7 +389,8 @@
      */
     private boolean shouldStartScoForUid(int uid) {
         return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
-                || UserHandle.isSameApp(uid, Process.PHONE_UID));
+                || UserHandle.isSameApp(uid, Process.PHONE_UID)
+                || UserHandle.isSameApp(uid, Process.SYSTEM_UID));
     }
 
     @GuardedBy("mDeviceStateLock")
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 03fc60c..cd0a2a7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -106,7 +106,8 @@
     protected final String TAG = getClass().getSimpleName().replace('$', '.');
     protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
+    protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
+    protected static final int ON_BINDING_DIED_REBIND_MSG = 1234;
     protected static final String ENABLED_SERVICES_SEPARATOR = ":";
     private static final String DB_VERSION_1 = "1";
     private static final String DB_VERSION_2 = "2";
@@ -856,7 +857,13 @@
             String approvedItem = getApprovedValue(pkgOrComponent);
 
             if (approvedItem != null) {
+                final ComponentName component = ComponentName.unflattenFromString(approvedItem);
                 if (enabled) {
+                    if (component != null && !isValidService(component, userId)) {
+                        Log.e(TAG, "Skip allowing " + mConfig.caption + " " + pkgOrComponent
+                                + " (userSet: " + userSet + ") for invalid service");
+                        return;
+                    }
                     approved.add(approvedItem);
                 } else {
                     approved.remove(approvedItem);
@@ -954,7 +961,7 @@
                 || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
             return false;
         }
-        return componentHasBindPermission(component, userId);
+        return isValidService(component, userId);
     }
 
     private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1306,11 +1313,12 @@
                     if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
                         final ComponentName component = ComponentName.unflattenFromString(
                                 approvedPackageOrComponent);
-                        if (component != null && !componentHasBindPermission(component, userId)) {
+                        if (component != null && !isValidService(component, userId)) {
                             approved.removeAt(j);
                             if (DEBUG) {
                                 Slog.v(TAG, "Removing " + approvedPackageOrComponent
-                                        + " from approved list; no bind permission found "
+                                        + " from approved list; no bind permission or "
+                                        + "service interface filter found "
                                         + mConfig.bindPermission);
                             }
                         }
@@ -1329,6 +1337,11 @@
         }
     }
 
+    protected boolean isValidService(ComponentName component, int userId) {
+        return componentHasBindPermission(component, userId) && queryPackageForServices(
+                component.getPackageName(), userId).contains(component);
+    }
+
     protected boolean isValidEntry(String packageOrComponent, int userId) {
         return hasMatchingServices(packageOrComponent, userId);
     }
@@ -1486,23 +1499,25 @@
      * Called when user switched to unbind all services from other users.
      */
     @VisibleForTesting
-    void unbindOtherUserServices(int currentUser) {
+    void unbindOtherUserServices(int switchedToUser) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
-        unbindServicesImpl(currentUser, true /* allExceptUser */);
+        t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser);
+        unbindServicesImpl(switchedToUser, true /* allExceptUser */);
         t.traceEnd();
     }
 
-    void unbindUserServices(int user) {
+    void unbindUserServices(int removedUser) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        t.traceBegin("ManagedServices.unbindUserServices" + user);
-        unbindServicesImpl(user, false /* allExceptUser */);
+        t.traceBegin("ManagedServices.unbindUserServices" + removedUser);
+        unbindServicesImpl(removedUser, false /* allExceptUser */);
         t.traceEnd();
     }
 
     void unbindServicesImpl(int user, boolean allExceptUser) {
         final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
         synchronized (mMutex) {
+            // Remove enqueued rebinds to avoid rebinding services for a switched user
+            mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG);
             final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
             for (ManagedServiceInfo info : removableBoundServices) {
                 if ((allExceptUser && (info.userid != user))
@@ -1697,6 +1712,7 @@
                             mServicesRebinding.add(servicesBindingTag);
                             mHandler.postDelayed(() ->
                                     reregisterService(name, userid),
+                                    ON_BINDING_DIED_REBIND_MSG,
                                     ON_BINDING_DIED_REBIND_DELAY_MS);
                         } else {
                             Slog.v(TAG, getCaption() + " not rebinding in user " + userid
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index bd01351..c84711d 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -106,6 +107,16 @@
         return isChangeEnabled(mActivityRecord, OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
     }
 
+    boolean shouldRespectRequestedOrientationDueToOverride() {
+        // Checking TaskFragment rather than ActivityRecord to ensure that transition
+        // between fullscreen and PiP would work well. Checking TaskFragment rather than
+        // Task to ensure that Activity Embedding is excluded.
+        return mActivityRecord.isVisibleRequested() && mActivityRecord.getTaskFragment() != null
+                && mActivityRecord.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+                    .isOverrideRespectRequestedOrientationEnabled();
+    }
+
     /**
      * Whether an app is calling {@link android.app.Activity#setRequestedOrientation}
      * in a loop and orientation request should be ignored.
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 0416f74..29ffda7 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -260,15 +259,14 @@
         if (mDisplayContent == null) {
             return false;
         }
-        ActivityRecord activity = mDisplayContent.topRunningActivity(
-                /* considerKeyguardState= */ true);
-        return activity != null && activity.getTaskFragment() != null
-                // Checking TaskFragment rather than ActivityRecord to ensure that transition
-                // between fullscreen and PiP would work well. Checking TaskFragment rather than
-                // Task to ensure that Activity Embedding is excluded.
-                && activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                && activity.mAppCompatController.getAppCompatOrientationOverrides()
-                    .isOverrideRespectRequestedOrientationEnabled();
+
+        // Top running activity can be freeform and ignore orientation request from bottom activity
+        // that should be respected, Check all activities in display to make sure any eligible
+        // activity should be respected.
+        final ActivityRecord activity = mDisplayContent.getActivity((r) ->
+                r.mAppCompatController.getAppCompatOrientationOverrides()
+                    .shouldRespectRequestedOrientationDueToOverride());
+        return activity != null;
     }
 
     boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c062f5a..04a625b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -79,6 +79,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.window.flags.Flags.enableFullyImmersiveInDesktop;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -2515,10 +2516,16 @@
                 defaultTaskDisplayArea.getRootTask(task -> task.isVisible()
                         && task.getTopLeafTask().getAdjacentTask() != null)
                         != null;
-        final boolean freeformRootTaskVisible =
-                defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
+        final Task topFreeformTask = defaultTaskDisplayArea
+                .getTopRootTaskInWindowingMode(WINDOWING_MODE_FREEFORM);
+        final boolean freeformRootTaskVisible = topFreeformTask != null
+                && topFreeformTask.isVisible();
+        final boolean inNonFullscreenFreeformMode = freeformRootTaskVisible
+                && !topFreeformTask.getBounds().equals(mDisplayContent.getBounds());
 
-        getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible);
+        getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible,
+                enableFullyImmersiveInDesktop()
+                        ? inNonFullscreenFreeformMode : freeformRootTaskVisible);
 
         final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
         if (getStatusBar() != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 61b13a8..24a6f118 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -628,8 +628,9 @@
         return (mForcedShowingTypes & types) == types;
     }
 
-    void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) {
-        mForcedShowingTypes = (inSplitScreenMode || inFreeformMode)
+    void updateSystemBars(WindowState win, boolean inSplitScreenMode,
+            boolean inNonFullscreenFreeformMode) {
+        mForcedShowingTypes = (inSplitScreenMode || inNonFullscreenFreeformMode)
                 ? (Type.statusBars() | Type.navigationBars())
                 : forceShowingNavigationBars(win)
                         ? Type.navigationBars()
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 48bc9d7..3bbc6b2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -63,11 +63,13 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
+import android.testing.TestableLooper;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -82,6 +84,7 @@
 
 import com.google.android.collect.Lists;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -103,6 +106,7 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
+
 public class ManagedServicesTest extends UiServiceTestCase {
 
     @Mock
@@ -115,6 +119,7 @@
     private ManagedServices.UserProfiles mUserProfiles;
     @Mock private DevicePolicyManager mDpm;
     Object mLock = new Object();
+    private TestableLooper mTestableLooper;
 
     UserInfo mZero = new UserInfo(0, "zero", 0);
     UserInfo mTen = new UserInfo(10, "ten", 0);
@@ -142,6 +147,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mTestableLooper = new TestableLooper(Looper.getMainLooper());
 
         mContext.setMockPackageManager(mPm);
         mContext.addMockSystemService(Context.USER_SERVICE, mUm);
@@ -199,6 +205,11 @@
                 mIpm, APPROVAL_BY_COMPONENT);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mTestableLooper.destroy();
+    }
+
     @Test
     public void testBackupAndRestore_migration() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -888,7 +899,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
         service.addApprovedList("a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -919,7 +930,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
         service.addApprovedList("a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -950,7 +961,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
         service.addApprovedList("a/a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -981,7 +992,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
         service.addApprovedList("a/a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -1053,6 +1064,77 @@
     }
 
     @Test
+    public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception {
+        Context context = mock(Context.class);
+        PackageManager pm = mock(PackageManager.class);
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        when(context.getPackageName()).thenReturn(mPkg);
+        when(context.getUserId()).thenReturn(mUser.getIdentifier());
+        when(context.getPackageManager()).thenReturn(pm);
+        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_PACKAGE);
+        service = spy(service);
+        ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+        // Trigger onBindingDied for component when registering
+        //  => will schedule a rebind in 10 seconds
+        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            ServiceConnection sc = (ServiceConnection) args[1];
+            sc.onBindingDied(cn);
+            return true;
+        });
+        service.registerService(cn, 0);
+        assertThat(service.isBound(cn, 0)).isFalse();
+
+        // Switch to user 10
+        service.onUserSwitched(10);
+
+        // Check that the scheduled rebind for user 0 was cleared
+        mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
+        mTestableLooper.processAllMessages();
+        verify(service, never()).reregisterService(any(), anyInt());
+    }
+
+    @Test
+    public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception {
+        Context context = mock(Context.class);
+        PackageManager pm = mock(PackageManager.class);
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        when(context.getPackageName()).thenReturn(mPkg);
+        when(context.getUserId()).thenReturn(mUser.getIdentifier());
+        when(context.getPackageManager()).thenReturn(pm);
+        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_PACKAGE);
+        service = spy(service);
+        ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+        // Trigger onBindingDied for component when registering
+        //  => will schedule a rebind in 10 seconds
+        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            ServiceConnection sc = (ServiceConnection) args[1];
+            sc.onBindingDied(cn);
+            return true;
+        });
+        service.registerService(cn, 0);
+        assertThat(service.isBound(cn, 0)).isFalse();
+
+        // Check that the scheduled rebind is run
+        mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
+        mTestableLooper.processAllMessages();
+        verify(service, times(1)).reregisterService(eq(cn), eq(0));
+    }
+
+    @Test
     public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1211,6 +1293,64 @@
     }
 
     @Test
+    public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
+        Context context = spy(getContext());
+        doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+                mIpm, APPROVAL_BY_COMPONENT);
+
+        List<String> packages = new ArrayList<>();
+        packages.add("package");
+        addExpectedServices(service, packages, 0);
+
+        final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+        final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+        // Both components are approved initially
+        mExpectedPrimaryComponentNames.clear();
+        mExpectedPrimaryPackages.clear();
+        mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+        mExpectedSecondaryComponentNames.clear();
+        mExpectedSecondaryPackages.clear();
+
+        loadXml(service);
+
+        //Component package/C1 loses serviceInterface intent filter
+        ManagedServices.Config config = service.getConfig();
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenAnswer(new Answer<List<ResolveInfo>>() {
+                    @Override
+                    public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+                            throws Throwable {
+                        Object[] args = invocationOnMock.getArguments();
+                        Intent invocationIntent = (Intent) args[0];
+                        if (invocationIntent != null) {
+                            if (invocationIntent.getAction().equals(config.serviceInterface)
+                                    && packages.contains(invocationIntent.getPackage())) {
+                                List<ResolveInfo> dummyServices = new ArrayList<>();
+                                ResolveInfo resolveInfo = new ResolveInfo();
+                                ServiceInfo serviceInfo = new ServiceInfo();
+                                serviceInfo.packageName = invocationIntent.getPackage();
+                                serviceInfo.name = approvedComponent.getClassName();
+                                serviceInfo.permission = service.getConfig().bindPermission;
+                                resolveInfo.serviceInfo = serviceInfo;
+                                dummyServices.add(resolveInfo);
+                                return dummyServices;
+                            }
+                        }
+                        return new ArrayList<>();
+                    }
+                });
+
+        // Trigger package update
+        service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+        assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+        assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+    }
+
+    @Test
     public void testSetPackageOrComponentEnabled() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1223,6 +1363,21 @@
                             "user10package1/K", "user10.3/Component", "user10package2/L",
                             "user10.4/Component"}));
 
+            // mock permissions for services
+            PackageManager pm = mock(PackageManager.class);
+            when(getContext().getPackageManager()).thenReturn(pm);
+            List<ComponentName> enabledComponents = List.of(
+                    ComponentName.unflattenFromString("package/Comp"),
+                    ComponentName.unflattenFromString("package/C2"),
+                    ComponentName.unflattenFromString("again/M4"),
+                    ComponentName.unflattenFromString("user10package/B"),
+                    ComponentName.unflattenFromString("user10/Component"),
+                    ComponentName.unflattenFromString("user10package1/K"),
+                    ComponentName.unflattenFromString("user10.3/Component"),
+                    ComponentName.unflattenFromString("user10package2/L"),
+                    ComponentName.unflattenFromString("user10.4/Component"));
+            mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>());
+
             for (int userId : expectedEnabled.keySet()) {
                 ArrayList<String> expectedForUser = expectedEnabled.get(userId);
                 for (int i = 0; i < expectedForUser.size(); i++) {
@@ -1944,7 +2099,7 @@
         metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
         metaDatas.put(cn_allowed, metaDataAutobindAllow);
 
-        mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+        mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
 
         service.addApprovedList(cn_allowed.flattenToString(), 0, true);
         service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -1989,7 +2144,7 @@
         metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
         metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
 
-        mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+        mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
 
         service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
 
@@ -2028,7 +2183,7 @@
         metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
         metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
 
-        mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+        mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
 
         service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
 
@@ -2099,8 +2254,8 @@
     }
 
     private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
-            ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
-            throws RemoteException {
+            ManagedServices service, PackageManager packageManager,
+            ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
         when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
                 (Answer<ServiceInfo>) invocation -> {
                     ComponentName invocationCn = invocation.getArgument(0);
@@ -2115,6 +2270,39 @@
                     return null;
                 }
         );
+
+        // add components to queryIntentServicesAsUser response
+        final List<String> packages = new ArrayList<>();
+        for (ComponentName cn: componentNames) {
+            packages.add(cn.getPackageName());
+        }
+        ManagedServices.Config config = service.getConfig();
+        when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
+                thenAnswer(new Answer<List<ResolveInfo>>() {
+                @Override
+                public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+                    throws Throwable {
+                    Object[] args = invocationOnMock.getArguments();
+                    Intent invocationIntent = (Intent) args[0];
+                    if (invocationIntent != null) {
+                        if (invocationIntent.getAction().equals(config.serviceInterface)
+                            && packages.contains(invocationIntent.getPackage())) {
+                            List<ResolveInfo> dummyServices = new ArrayList<>();
+                            for (ComponentName cn: componentNames) {
+                                ResolveInfo resolveInfo = new ResolveInfo();
+                                ServiceInfo serviceInfo = new ServiceInfo();
+                                serviceInfo.packageName = invocationIntent.getPackage();
+                                serviceInfo.name = cn.getClassName();
+                                serviceInfo.permission = service.getConfig().bindPermission;
+                                resolveInfo.serviceInfo = serviceInfo;
+                                dummyServices.add(resolveInfo);
+                            }
+                            return dummyServices;
+                        }
+                    }
+                    return new ArrayList<>();
+                }
+            });
     }
 
     private void resetComponentsAndPackages() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 797b95b5..7e4ae67 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -25,6 +25,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNull;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
@@ -193,6 +194,8 @@
     public void testWriteXml_userTurnedOffNAS() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
+        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
+
         mAssistants.loadDefaultsFromConfig(true);
 
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
@@ -398,6 +401,10 @@
     public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
         ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
         ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
+
+        doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id));
+        doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id));
+
         mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true,
                 true, true);
         verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal(
@@ -543,6 +550,7 @@
     public void testSetAdjustmentTypeSupportedState() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
+        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
                 true, true);
@@ -566,6 +574,7 @@
     public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
+        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
                 true, true);
@@ -589,6 +598,7 @@
     public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
+        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
                 true, true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 03cad24..592eec5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2291,10 +2291,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         NotificationRecord r = getBeepyNotification();
 
@@ -2338,10 +2335,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         NotificationRecord r = getBeepyNotification();
 
@@ -2379,10 +2373,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         NotificationRecord r = getBeepyNotification();
 
@@ -2428,10 +2419,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         NotificationRecord r = getBeepyNotification();
         r.getNotification().category = Notification.CATEGORY_EVENT;
@@ -2504,10 +2492,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         // Regular notification: should beep at 0% volume
         NotificationRecord r = getBeepyNotification();
@@ -2574,10 +2559,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         NotificationRecord r = getBeepyNotification();
 
@@ -2602,10 +2584,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         // CATEGORY_ALARM is exempted
         NotificationRecord r = getBeepyNotification();
@@ -2646,10 +2625,7 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
-        // Trigger avalanche trigger intent
-        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        intent.putExtra("state", false);
-        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        triggerAvalancheEvent();
 
         // Create a conversation group with GROUP_ALERT_SUMMARY behavior
         // Where the summary is not MessagingStyle
@@ -2693,6 +2669,16 @@
         assertEquals(-1, summary.getLastAudiblyAlertedMs());
     }
 
+    private void triggerAvalancheEvent() throws Exception {
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+        // Wait after avalanche trigger before posting notifications
+        // so that notification#getWhen() is not the same value
+        Thread.sleep(100);
+    }
+
     @Test
     public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index d9b5f37..8747cfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -17,11 +17,13 @@
 
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
 import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP;
 import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS;
@@ -31,6 +33,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.compat.testing.PlatformCompatChangeRule;
+import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -228,6 +231,25 @@
         });
     }
 
+    @Test
+    public void testOverrideRespectRequestedOrientationIsEnabled_bottomOrientationIsRespected() {
+        runTestScenario((robot) -> {
+            robot.applyOnActivity((a) -> {
+                a.setIgnoreOrientationRequest(true);
+                a.createActivityWithComponentInNewTask();
+                robot.setOverrideRespectRequestedOrientationEnabled(true);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_LANDSCAPE);
+                robot.checkDisplayShouldIgnoreOrientationRequest(SCREEN_ORIENTATION_LANDSCAPE,
+                        /* expected */ false);
+
+                a.createActivityWithComponentInNewTask();
+                a.setTopActivityInFreeformWindowingMode(true);
+            });
+            robot.checkDisplayShouldIgnoreOrientationRequest(SCREEN_ORIENTATION_LANDSCAPE,
+                    /* expected */ false);
+        });
+    }
+
     /**
      * Runs a test scenario providing a Robot.
      */
@@ -291,6 +313,22 @@
             }
         }
 
+        void setOverrideRespectRequestedOrientationEnabled(boolean override) {
+            spyOn(getTopOrientationOverrides());
+            doReturn(override).when(getTopOrientationOverrides())
+                    .isOverrideRespectRequestedOrientationEnabled();
+        }
+
+        void checkDisplayShouldIgnoreOrientationRequest(@ScreenOrientation int candidate,
+                boolean expected) {
+            assertEquals(expected, activity().displayContent()
+                    .shouldIgnoreOrientationRequest(candidate));
+        }
+
+        void checkExpectedDisplayOrientation(@ScreenOrientation int expected) {
+            assertEquals(expected, activity().displayContent().getOrientation());
+        }
+
         void checkShouldUseDisplayLandscapeNaturalOrientation(boolean expected) {
             assertEquals(expected,
                     getTopOrientationOverrides().shouldUseDisplayLandscapeNaturalOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index b26c267..d2cf03d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -41,7 +41,10 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.StatusBarManager;
+import android.graphics.Rect;
 import android.os.Binder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
@@ -52,6 +55,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -95,6 +99,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     public void testControlsForDispatch_freeformTaskVisible() {
         addStatusBar();
         addNavigationBar();
@@ -108,6 +113,37 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    public void testControlsForDispatch_fullscreenFreeformTaskVisible() {
+        addStatusBar();
+        addNavigationBar();
+
+        final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        win.setBounds(new Rect());
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The freeform (w/fullscreen bounds) app window can control both system bars.
+        assertNotNull(controls);
+        assertEquals(2, controls.length);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    public void testControlsForDispatch_nonFullscreenFreeformTaskVisible() {
+        addStatusBar();
+        addNavigationBar();
+
+        final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        win.getTask().setBounds(new Rect(1, 1, 10, 10));
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The freeform (but not fullscreen bounds) app window must not control any system bars.
+        assertNull(controls);
+    }
+
+    @Test
     public void testControlsForDispatch_forceStatusBarVisible() {
         addStatusBar().mAttrs.forciblyShownTypes |= statusBars();
         addNavigationBar();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff966ae..f01cfc1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2741,7 +2741,7 @@
 
     /**
      * Returns a constant indicating the device phone type.  This
-     * indicates the type of radio used to transmit voice calls.
+     * indicates the type of radio used to transmit voice/data calls.
      *
      * @see #PHONE_TYPE_NONE
      * @see #PHONE_TYPE_GSM
@@ -2753,7 +2753,7 @@
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public int getPhoneType() {
-        if (!isVoiceCapable()) {
+        if (!isVoiceCapable() && !isDataCapable()) {
             return PHONE_TYPE_NONE;
         }
         return getCurrentPhoneType();