Merge "Fix media output component appearance" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 7dc15fb..9ae5afb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7239,8 +7239,8 @@
 
   public final class PictureInPictureUiState implements android.os.Parcelable {
     method public int describeContents();
-    method @FlaggedApi("android.app.enable_pip_ui_state_callback_on_entering") public boolean isEnteringPip();
     method public boolean isStashed();
+    method @FlaggedApi("android.app.enable_pip_ui_state_callback_on_entering") public boolean isTransitioningToPip();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.PictureInPictureUiState> CREATOR;
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 7c4df28..9c1a8e8 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -135,7 +135,7 @@
 
   @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public class SignedPackage {
     method @NonNull public byte[] getCertificateDigest();
-    method @NonNull public String getPkgName();
+    method @NonNull public String getPackageName();
   }
 
 }
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 4cad585..c58624e 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -26,6 +26,7 @@
 import android.util.LongArray;
 
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * This is the superclass for classes which provide basic support for animations which can be
@@ -76,7 +77,7 @@
      * of it in case the list is modified while iterating. The array can be reused to avoid
      * allocation on every notification.
      */
-    private Object[] mCachedList;
+    private AtomicReference<Object[]> mCachedList = new AtomicReference<>();
 
     /**
      * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
@@ -452,7 +453,7 @@
             if (mPauseListeners != null) {
                 anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
             }
-            anim.mCachedList = null;
+            anim.mCachedList.set(null);
             anim.mStartListenersCalled = false;
             return anim;
         } catch (CloneNotSupportedException e) {
@@ -654,13 +655,9 @@
         int size = list == null ? 0 : list.size();
         if (size > 0) {
             // Try to reuse mCacheList to store the items of list.
-            Object[] array;
-            if (mCachedList == null || mCachedList.length < size) {
+            Object[] array = mCachedList.getAndSet(null);
+            if (array == null || array.length < size) {
                 array = new Object[size];
-            } else {
-                array = mCachedList;
-                // Clear it in case there is some reentrancy
-                mCachedList = null;
             }
             list.toArray(array);
             for (int i = 0; i < size; i++) {
@@ -670,7 +667,7 @@
                 array[i] = null;
             }
             // Store it for the next call so we can reuse this array, if needed.
-            mCachedList = array;
+            mCachedList.compareAndSet(null, array);
         }
     }
 
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index da0cc01..41b97d0 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -118,6 +118,8 @@
 # Multitasking
 per-file multitasking.aconfig = file:/services/core/java/com/android/server/wm/OWNERS
 per-file multitasking.aconfig = file:/libs/WindowManager/Shell/OWNERS
+per-file PictureInPicture* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file PictureInPicture* = file:/libs/WindowManager/Shell/OWNERS
 
 # Zygote
 per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/PictureInPictureUiState.java b/core/java/android/app/PictureInPictureUiState.java
index 39ba54c..1629536 100644
--- a/core/java/android/app/PictureInPictureUiState.java
+++ b/core/java/android/app/PictureInPictureUiState.java
@@ -31,12 +31,12 @@
 public final class PictureInPictureUiState implements Parcelable {
 
     private final boolean mIsStashed;
-    private final boolean mIsEnteringPip;
+    private final boolean mIsTransitioningToPip;
 
     /** {@hide} */
     PictureInPictureUiState(Parcel in) {
         mIsStashed = in.readBoolean();
-        mIsEnteringPip = in.readBoolean();
+        mIsTransitioningToPip = in.readBoolean();
     }
 
     /** {@hide} */
@@ -45,9 +45,9 @@
         this(isStashed, false /* isEnteringPip */);
     }
 
-    private PictureInPictureUiState(boolean isStashed, boolean isEnteringPip) {
+    private PictureInPictureUiState(boolean isStashed, boolean isTransitioningToPip) {
         mIsStashed = isStashed;
-        mIsEnteringPip = isEnteringPip;
+        mIsTransitioningToPip = isTransitioningToPip;
     }
 
     /**
@@ -77,14 +77,14 @@
      * whether via auto enter PiP or calling
      * {@link Activity#enterPictureInPictureMode(PictureInPictureParams)} explicitly, app can expect
      * {@link Activity#onPictureInPictureUiStateChanged(PictureInPictureUiState)} callback with
-     * {@link #isEnteringPip()} to be {@code true} first,
+     * {@link #isTransitioningToPip()} to be {@code true} first,
      * followed by {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)} when it
      * fully settles in PiP mode.
      *
      * When app receives the
      * {@link Activity#onPictureInPictureUiStateChanged(PictureInPictureUiState)} callback with
-     * {@link #isEnteringPip()} being {@code true}, it's recommended to hide certain UI elements,
-     * such as video controls, to archive a clean entering PiP animation.
+     * {@link #isTransitioningToPip()} being {@code true}, it's recommended to hide certain UI
+     * elements, such as video controls, to archive a clean entering PiP animation.
      *
      * In case an application wants to restore the previously hidden UI elements when exiting
      * PiP, it is recommended to do that in
@@ -92,8 +92,8 @@
      * than the beginning of exit PiP animation.
      */
     @FlaggedApi(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
-    public boolean isEnteringPip() {
-        return mIsEnteringPip;
+    public boolean isTransitioningToPip() {
+        return mIsTransitioningToPip;
     }
 
     @Override
@@ -102,12 +102,12 @@
         if (!(o instanceof PictureInPictureUiState)) return false;
         PictureInPictureUiState that = (PictureInPictureUiState) o;
         return mIsStashed == that.mIsStashed
-                && mIsEnteringPip == that.mIsEnteringPip;
+                && mIsTransitioningToPip == that.mIsTransitioningToPip;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mIsStashed, mIsEnteringPip);
+        return Objects.hash(mIsStashed, mIsTransitioningToPip);
     }
 
     @Override
@@ -118,7 +118,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeBoolean(mIsStashed);
-        out.writeBoolean(mIsEnteringPip);
+        out.writeBoolean(mIsTransitioningToPip);
     }
 
     public static final @android.annotation.NonNull Creator<PictureInPictureUiState> CREATOR =
@@ -138,7 +138,7 @@
     @FlaggedApi(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
     public static final class Builder {
         private boolean mIsStashed;
-        private boolean mIsEnteringPip;
+        private boolean mIsTransitioningToPip;
 
         /** Empty constructor. */
         public Builder() {
@@ -154,11 +154,11 @@
         }
 
         /**
-         * Sets the {@link #mIsEnteringPip} state.
+         * Sets the {@link #mIsTransitioningToPip} state.
          * @return The same {@link Builder} instance.
          */
-        public Builder setEnteringPip(boolean isEnteringPip) {
-            mIsEnteringPip = isEnteringPip;
+        public Builder setTransitioningToPip(boolean isEnteringPip) {
+            mIsTransitioningToPip = isEnteringPip;
             return this;
         }
 
@@ -166,7 +166,7 @@
          * @return The constructed {@link PictureInPictureUiState} instance.
          */
         public PictureInPictureUiState build() {
-            return new PictureInPictureUiState(mIsStashed, mIsEnteringPip);
+            return new PictureInPictureUiState(mIsStashed, mIsTransitioningToPip);
         }
     }
 }
diff --git a/core/java/android/content/pm/SignedPackage.java b/core/java/android/content/pm/SignedPackage.java
index 4d1b136..7bffa77 100644
--- a/core/java/android/content/pm/SignedPackage.java
+++ b/core/java/android/content/pm/SignedPackage.java
@@ -35,9 +35,9 @@
     private final SignedPackageParcel mData;
 
     /** @hide */
-    public SignedPackage(@NonNull String pkgName, @NonNull byte[] certificateDigest) {
+    public SignedPackage(@NonNull String packageName, @NonNull byte[] certificateDigest) {
         SignedPackageParcel data = new SignedPackageParcel();
-        data.pkgName = pkgName;
+        data.packageName = packageName;
         data.certificateDigest = certificateDigest;
         mData = data;
     }
@@ -52,8 +52,8 @@
         return mData;
     }
 
-    public @NonNull String getPkgName() {
-        return mData.pkgName;
+    public @NonNull String getPackageName() {
+        return mData.packageName;
     }
 
     public @NonNull byte[] getCertificateDigest() {
@@ -64,12 +64,12 @@
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!(o instanceof SignedPackage that)) return false;
-        return mData.pkgName.equals(that.mData.pkgName) && Arrays.equals(mData.certificateDigest,
-                that.mData.certificateDigest);
+        return mData.packageName.equals(that.mData.packageName) && Arrays.equals(
+                mData.certificateDigest, that.mData.certificateDigest);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mData.pkgName, Arrays.hashCode(mData.certificateDigest));
+        return Objects.hash(mData.packageName, Arrays.hashCode(mData.certificateDigest));
     }
 }
diff --git a/core/java/android/content/pm/SignedPackageParcel.aidl b/core/java/android/content/pm/SignedPackageParcel.aidl
index 7957f7f..bb4dc86 100644
--- a/core/java/android/content/pm/SignedPackageParcel.aidl
+++ b/core/java/android/content/pm/SignedPackageParcel.aidl
@@ -20,6 +20,6 @@
 
 /** @hide */
 parcelable SignedPackageParcel {
-    String pkgName;
+    String packageName;
     byte[] certificateDigest;
 }
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 76c20ce..749f218 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -874,8 +874,11 @@
                 Class<CameraCharacteristics.Key<?>> keyTyped =
                         (Class<CameraCharacteristics.Key<?>>) key;
 
+                // Do not include synthetic keys. Including synthetic keys leads to undefined
+                // behavior. This causes inclusion of capabilities that may not be supported in
+                // camera extensions.
                 ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
-                        /*includeSynthetic*/ true));
+                        /*includeSynthetic*/ false));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension for all available keys! Extension "
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 311dc09..6efb872 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -9,3 +9,11 @@
   description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
   bug: "308011229"
 }
+
+flag {
+    name: "powered_off_finding_platform"
+    namespace: "nearby"
+    description: "Controls whether the Powered Off Finding feature is enabled"
+    bug: "307898240"
+}
+
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index c9c91fc..efbd96b 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -591,9 +591,14 @@
     /**
      * Scale given vibration intensity by the given factor.
      *
+     * <p> This scale is not necessarily linear and may apply a gamma correction to the scale
+     * factor before using it.
+     *
      * @param intensity   relative intensity of the effect, must be between 0 and 1
      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
      *                    scale down the intensity, values larger than 1 will scale up
+     * @return the scaled intensity which will be values within [0, 1].
+     *
      * @hide
      */
     public static float scale(float intensity, float scaleFactor) {
@@ -624,6 +629,20 @@
     }
 
     /**
+     * Performs a linear scaling on the given vibration intensity by the given factor.
+     *
+     * @param intensity relative intensity of the effect, must be between 0 and 1.
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up.
+     * @return the scaled intensity which will be values within [0, 1].
+     *
+     * @hide
+     */
+    public static float scaleLinearly(float intensity, float scaleFactor) {
+        return MathUtils.constrain(intensity * scaleFactor, 0f, 1f);
+    }
+
+    /**
      * Returns a compact version of the {@link #toString()} result for debugging purposes.
      *
      * @hide
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index a035092..39f8412 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -137,6 +137,14 @@
     /** @hide */
     @NonNull
     @Override
+    public PrebakedSegment scaleLinearly(float scaleFactor) {
+        // Prebaked effect strength cannot be scaled with this method.
+        return this;
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
     public PrebakedSegment applyEffectStrength(int effectStrength) {
         if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
             return new PrebakedSegment(mEffectId, mFallback, effectStrength);
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 95d97bf..3c84bcd 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -98,8 +98,24 @@
     @NonNull
     @Override
     public PrimitiveSegment scale(float scaleFactor) {
-        return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
-                mDelay);
+        float newScale = VibrationEffect.scale(mScale, scaleFactor);
+        if (Float.compare(mScale, newScale) == 0) {
+            return this;
+        }
+
+        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
+    public PrimitiveSegment scaleLinearly(float scaleFactor) {
+        float newScale = VibrationEffect.scaleLinearly(mScale, scaleFactor);
+        if (Float.compare(mScale, newScale) == 0) {
+            return this;
+        }
+
+        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
     }
 
     /** @hide */
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 5f9d102..09d2e26 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -159,6 +159,21 @@
     /** @hide */
     @NonNull
     @Override
+    public RampSegment scaleLinearly(float scaleFactor) {
+        float newStartAmplitude = VibrationEffect.scaleLinearly(mStartAmplitude, scaleFactor);
+        float newEndAmplitude = VibrationEffect.scaleLinearly(mEndAmplitude, scaleFactor);
+        if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
+                && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
+            return this;
+        }
+        return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz,
+                mEndFrequencyHz,
+                mDuration);
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
     public RampSegment applyEffectStrength(int effectStrength) {
         return this;
     }
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 9576a5b..fa083c1 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -137,8 +137,25 @@
         if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
             return this;
         }
-        return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequencyHz,
-                mDuration);
+        float newAmplitude = VibrationEffect.scale(mAmplitude, scaleFactor);
+        if (Float.compare(newAmplitude, mAmplitude) == 0) {
+            return this;
+        }
+        return new StepSegment(newAmplitude, mFrequencyHz, mDuration);
+    }
+
+    /** @hide */
+    @NonNull
+    @Override
+    public StepSegment scaleLinearly(float scaleFactor) {
+        if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+            return this;
+        }
+        float newAmplitude = VibrationEffect.scaleLinearly(mAmplitude, scaleFactor);
+        if (Float.compare(newAmplitude, mAmplitude) == 0) {
+            return this;
+        }
+        return new StepSegment(newAmplitude, mFrequencyHz, mDuration);
     }
 
     /** @hide */
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 17ac36f..e1fb4e3 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -96,6 +96,9 @@
     /**
      * Scale the segment intensity with the given factor.
      *
+     * <p> This scale is not necessarily linear and may apply a gamma correction to the scale
+     * factor before using it.
+     *
      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
      *                    scale down the intensity, values larger than 1 will scale up
      *
@@ -105,6 +108,17 @@
     public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
 
     /**
+     * Performs a linear scaling on the segment intensity with the given factor.
+     *
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up
+     *
+     * @hide
+     */
+    @NonNull
+    public abstract <T extends VibrationEffectSegment> T scaleLinearly(float scaleFactor);
+
+    /**
      * Applies given effect strength to prebaked effects.
      *
      * @param effectStrength new effect strength to be applied, one of
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c475f6b..51d7caa 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,11 +69,11 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IGlobalDragListener;
 import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
-import android.window.IUnhandledDragListener;
 import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
@@ -1094,10 +1094,9 @@
     void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
 
     /**
-     * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
-     * (ie. not handled by any window which can handle the drag).
+     * Sets the listener to be called back when a cross-window drag and drop operation happens.
      */
-    void setUnhandledDragListener(IUnhandledDragListener listener);
+    void setGlobalDragListener(IGlobalDragListener listener);
 
     boolean transferTouchGesture(in InputTransferToken transferFromToken,
             in InputTransferToken transferToToken);
diff --git a/core/java/android/window/IUnhandledDragListener.aidl b/core/java/android/window/IGlobalDragListener.aidl
similarity index 78%
rename from core/java/android/window/IUnhandledDragListener.aidl
rename to core/java/android/window/IGlobalDragListener.aidl
index 52e9895..8f2ca02 100644
--- a/core/java/android/window/IUnhandledDragListener.aidl
+++ b/core/java/android/window/IGlobalDragListener.aidl
@@ -16,14 +16,21 @@
 
 package android.window;
 
+import android.app.ActivityManager;
 import android.view.DragEvent;
 import android.window.IUnhandledDragCallback;
 
 /**
- * An interface to a handler for global drags that are not consumed (ie. not handled by any window).
+ * An interface to a handler for global drags.
  * {@hide}
  */
-oneway interface IUnhandledDragListener {
+oneway interface IGlobalDragListener {
+    /**
+     * Called when a cross-window drag is handled by another window.
+     * @param taskInfo the task containing the window that consumed the drop
+     */
+    void onCrossWindowDrop(in ActivityManager.RunningTaskInfo taskInfo);
+
     /**
      * Called when the user finishes the drag gesture but no windows have reported handling the
      * drop.  The DragEvent is populated with the drag surface for the listener to animate.  The
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f93b306..a883144 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -378,8 +378,8 @@
                         ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str());
                     }
                     inputEventObj =
-                            android_view_KeyEvent_fromNative(env,
-                                                             static_cast<KeyEvent&>(*inputEvent));
+                            android_view_KeyEvent_obtainAsCopy(env,
+                                                               static_cast<KeyEvent&>(*inputEvent));
                     break;
 
                 case InputEventType::MOTION: {
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index b5fbb22..88b02ba 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -359,7 +359,7 @@
         jint seq, jobject eventObj) {
     sp<NativeInputEventSender> sender =
             reinterpret_cast<NativeInputEventSender*>(senderPtr);
-    const KeyEvent event = android_view_KeyEvent_toNative(env, eventObj);
+    const KeyEvent event = android_view_KeyEvent_obtainAsCopy(env, eventObj);
     status_t status = sender->sendKeyEvent(seq, &event);
     return !status;
 }
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index a0d081d..50d2cbe 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -221,7 +221,7 @@
         jboolean predispatch) {
     InputQueue* queue = reinterpret_cast<InputQueue*>(ptr);
     KeyEvent* event = queue->createKeyEvent();
-    *event = android_view_KeyEvent_toNative(env, eventObj);
+    *event = android_view_KeyEvent_obtainAsCopy(env, eventObj);
 
     if (predispatch) {
         event->setFlags(event->getFlags() | AKEY_EVENT_FLAG_PREDISPATCH);
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index a79e37a..3325e89 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -219,7 +219,7 @@
         result = env->NewObjectArray(jsize(events.size()), gKeyEventClassInfo.clazz, NULL);
         if (result) {
             for (size_t i = 0; i < events.size(); i++) {
-                jobject keyEventObj = android_view_KeyEvent_fromNative(env, events.itemAt(i));
+                jobject keyEventObj = android_view_KeyEvent_obtainAsCopy(env, events.itemAt(i));
                 if (!keyEventObj) break; // threw OOM exception
                 env->SetObjectArrayElement(result, jsize(i), keyEventObj);
                 env->DeleteLocalRef(keyEventObj);
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index a9c9919..908ab5e 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -94,7 +94,7 @@
 
 // ----------------------------------------------------------------------------
 
-jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event) {
+jobject android_view_KeyEvent_obtainAsCopy(JNIEnv* env, const KeyEvent& event) {
     ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event.getHmac());
     jobject eventObj =
             env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, gKeyEventClassInfo.obtain,
@@ -113,7 +113,7 @@
     return eventObj;
 }
 
-KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj) {
+KeyEvent android_view_KeyEvent_obtainAsCopy(JNIEnv* env, jobject eventObj) {
     jint id = env->GetIntField(eventObj, gKeyEventClassInfo.mId);
     jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
     jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource);
diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h
index bc4876a..eab7c97 100644
--- a/core/jni/android_view_KeyEvent.h
+++ b/core/jni/android_view_KeyEvent.h
@@ -27,11 +27,11 @@
 
 /* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance.
  * Returns NULL on error. */
-extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event);
+extern jobject android_view_KeyEvent_obtainAsCopy(JNIEnv* env, const KeyEvent& event);
 
 /* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance.
  * Returns non-zero on error. */
-extern KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj);
+extern KeyEvent android_view_KeyEvent_obtainAsCopy(JNIEnv* env, jobject eventObj);
 
 /* Recycles a DVM KeyEvent object.
  * Key events should only be recycled if they are owned by the system since user
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index ebf4cca..a7d083c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -988,7 +988,7 @@
 
         @Override
         public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
-            if (mPipUiStateLatch != null && pipState.isEnteringPip()) {
+            if (mPipUiStateLatch != null && pipState.isTransitioningToPip()) {
                 mPipUiStateLatch.countDown();
             }
         }
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 4f5f3c0..7dd9e55 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -106,6 +106,13 @@
     }
 
     @Test
+    public void testScaleLinearly_ignoresAndReturnsSameEffect() {
+        PrebakedSegment prebaked = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertSame(prebaked, prebaked.scaleLinearly(0.5f));
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(-1, new PrebakedSegment(
                 VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index ec5a084..e9a08ae 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -129,6 +129,27 @@
     }
 
     @Test
+    public void testScaleLinearly() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+        assertEquals(1f, initial.scaleLinearly(1).getScale(), TOLERANCE);
+        assertEquals(0.5f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE);
+        assertEquals(1f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE);
+        assertEquals(0.8f, initial.scaleLinearly(0.8f).getScale(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE);
+
+        initial = new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
+
+        assertEquals(0f, initial.scaleLinearly(1).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE);
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(-1, new PrimitiveSegment(
                 VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).getDuration());
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 5caa86b..01013ab 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -131,6 +131,37 @@
     }
 
     @Test
+    public void testScaleLinearly() {
+        RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+        assertEquals(0f, initial.scaleLinearly(1f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getStartAmplitude(),
+                TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(),
+                TOLERANCE);
+
+        assertEquals(1f, initial.scaleLinearly(1f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scaleLinearly(0.5f).getEndAmplitude(), TOLERANCE);
+        assertEquals(1f, initial.scaleLinearly(1.5f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.8f, initial.scaleLinearly(0.8f).getEndAmplitude(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(0.8f, initial.scaleLinearly(1.5f).scaleLinearly(0.8f).getEndAmplitude(),
+                TOLERANCE);
+
+        initial = new RampSegment(0.5f, 1, 0, 0, 0);
+
+        assertEquals(0.5f, initial.scaleLinearly(1).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.25f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.75f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.4f, initial.scaleLinearly(0.8f).getStartAmplitude(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(0.5f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(),
+                TOLERANCE);
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration());
     }
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 44db306..40776ab 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -141,6 +141,37 @@
     }
 
     @Test
+    public void testScaleLinearly_fullAmplitude() {
+        StepSegment initial = new StepSegment(1f, 0, 0);
+
+        assertEquals(1f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE);
+        assertEquals(1f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0.8f, initial.scaleLinearly(0.8f).getAmplitude(), TOLERANCE);
+        // Restores back to the exact original value since this is a linear scaling.
+        assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getAmplitude(),
+                TOLERANCE);
+
+        initial = new StepSegment(0, 0, 0);
+
+        assertEquals(0f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScaleLinearly_defaultAmplitude() {
+        StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
+
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1).getAmplitude(),
+                TOLERANCE);
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(0.5f).getAmplitude(),
+                TOLERANCE);
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1.5f).getAmplitude(),
+                TOLERANCE);
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(5, new StepSegment(0, 0, 5).getDuration());
     }
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index e04ab81..34f03c2 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -23,7 +23,8 @@
 
     <com.android.wm.shell.bubbles.bar.BubbleBarHandleView
         android:id="@+id/bubble_bar_handle_view"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
+        android:layout_height="@dimen/bubble_bar_expanded_view_caption_height"
+        android:layout_width="@dimen/bubble_bar_expanded_view_caption_width"
+        android:layout_gravity="top|center_horizontal" />
 
 </com.android.wm.shell.bubbles.bar.BubbleBarExpandedView>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f73775b..3b0eb49 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -244,6 +244,8 @@
     <dimen name="bubble_popup_padding">24dp</dimen>
     <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
     <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+    <!-- The width of the caption bar at the top of bubble bar expanded view. -->
+    <dimen name="bubble_bar_expanded_view_caption_width">128dp</dimen>
     <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
     <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
     <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index fe65fdd..d8d0d87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -643,19 +643,6 @@
         }
     }
 
-    /** Helper to set int metadata on the Surface corresponding to the task id. */
-    public void setSurfaceMetadata(int taskId, int key, int value) {
-        synchronized (mLock) {
-            final TaskAppearedInfo info = mTasks.get(taskId);
-            if (info == null || info.getLeash() == null) {
-                return;
-            }
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            t.setMetadata(info.getLeash(), key, value);
-            t.apply();
-        }
-    }
-
     private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
             TaskListener oldListener, TaskListener newListener) {
         if (oldListener == newListener) return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 5c6f73f..b123c28c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1838,7 +1838,7 @@
                     + " expanded=%b selectionChanged=%b selected=%s"
                     + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
                     update.addedBubble != null ? update.addedBubble.getKey() : "null",
-                    update.removedBubbles.isEmpty(),
+                    !update.removedBubbles.isEmpty(),
                     update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
                     update.orderChanged, update.expandedChanged, update.expanded,
                     update.selectionChanged,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index eddd43f..271fb9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -143,6 +143,8 @@
                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCurrentCornerRadius);
             }
         });
+        // Set a touch sink to ensure that clicks on the caption area do not propagate to the parent
+        setOnTouchListener((v, event) -> true);
     }
 
     @Override
@@ -245,12 +247,8 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-        int menuViewHeight = Math.min(mCaptionHeight, height);
-        measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
-                MeasureSpec.getMode(heightMeasureSpec)));
-
         if (mTaskView != null) {
+            int height = MeasureSpec.getSize(heightMeasureSpec);
             measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
                     MeasureSpec.getMode(heightMeasureSpec)));
         }
@@ -259,14 +257,11 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        final int captionBottom = t + mCaptionHeight;
         if (mTaskView != null) {
             mTaskView.layout(l, t, r,
                     t + mTaskView.getMeasuredHeight());
             mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
         }
-        // Handle draws on top of task view in the caption area.
-        mHandleView.layout(l, t, r, captionBottom);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62f2726..78a41f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -231,6 +231,7 @@
             // Touch delegate for the menu
             BubbleBarHandleView view = mExpandedView.getHandleView();
             view.getBoundsOnScreen(mHandleTouchBounds);
+            // Move top value up to ensure touch target is large enough
             mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
             mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
                     mExpandedView.getHandleView());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index a9f687f..6ffeb97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -125,11 +125,15 @@
     public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
         final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
 
-        final Rect destinationBounds = reentryState != null
-                ? getDefaultBounds(reentryState.getSnapFraction(), reentryState.getSize())
-                : getDefaultBounds();
+        final Rect destinationBounds = getDefaultBounds();
+        if (reentryState != null) {
+            final Size scaledBounds = new Size(
+                    Math.round(mPipBoundsState.getMaxSize().x * reentryState.getBoundsScale()),
+                    Math.round(mPipBoundsState.getMaxSize().y * reentryState.getBoundsScale()));
+            destinationBounds.set(getDefaultBounds(reentryState.getSnapFraction(), scaledBounds));
+        }
 
-        final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
+        final boolean useCurrentSize = reentryState != null;
         Rect aspectRatioBounds = transformBoundsToAspectRatioIfValid(destinationBounds,
                 mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
                 useCurrentSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index df589df..b57e2d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -298,8 +298,8 @@
     }
 
     /** Save the reentry state to restore to when re-entering PIP mode. */
-    public void saveReentryState(Size size, float fraction) {
-        mPipReentryState = new PipReentryState(size, fraction);
+    public void saveReentryState(float fraction) {
+        mPipReentryState = new PipReentryState(mBoundsScale, fraction);
     }
 
     /** Returns the saved reentry state. */
@@ -601,17 +601,16 @@
     public static final class PipReentryState {
         private static final String TAG = PipReentryState.class.getSimpleName();
 
-        private final @Nullable Size mSize;
         private final float mSnapFraction;
+        private final float mBoundsScale;
 
-        PipReentryState(@Nullable Size size, float snapFraction) {
-            mSize = size;
+        PipReentryState(float boundsScale, float snapFraction) {
+            mBoundsScale = boundsScale;
             mSnapFraction = snapFraction;
         }
 
-        @Nullable
-        public Size getSize() {
-            return mSize;
+        public float getBoundsScale() {
+            return mBoundsScale;
         }
 
         public float getSnapFraction() {
@@ -621,7 +620,7 @@
         void dump(PrintWriter pw, String prefix) {
             final String innerPrefix = prefix + "  ";
             pw.println(prefix + TAG);
-            pw.println(innerPrefix + "mSize=" + mSize);
+            pw.println(innerPrefix + "mBoundsScale=" + mBoundsScale);
             pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
         }
     }
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 4fe79c1..aded115 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
@@ -65,7 +65,7 @@
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.draganddrop.UnhandledDragController;
+import com.android.wm.shell.draganddrop.GlobalDragListener;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -562,10 +562,10 @@
 
     @WMSingleton
     @Provides
-    static UnhandledDragController provideUnhandledDragController(
+    static GlobalDragListener provideGlobalDragListener(
             IWindowManager wmService,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new UnhandledDragController(wmService, mainExecutor);
+        return new GlobalDragListener(wmService, mainExecutor);
     }
 
     @WMSingleton
@@ -577,9 +577,12 @@
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
+            GlobalDragListener globalDragListener,
+            Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new DragAndDropController(context, shellInit, shellController,
-                shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor);
+        return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+                displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
+                mainExecutor);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6de5d74..645ff06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -255,7 +255,10 @@
                 2 -> {
                     // Split-screen case where there are two focused tasks, then we find the child
                     // task to move to desktop.
-                    val splitFocusedTask = findChildFocusedTask(allFocusedTasks)
+                    val splitFocusedTask =
+                        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+                            allFocusedTasks[1]
+                        else allFocusedTasks[0]
                     moveToDesktop(splitFocusedTask)
                 }
                 1 -> {
@@ -263,21 +266,17 @@
                     moveToDesktop(allFocusedTasks[0].taskId)
                 }
                 else -> {
-                    KtProtoLog.v(
+                    KtProtoLog.w(
                         WM_SHELL_DESKTOP_MODE,
-                        "DesktopTasksController: Cannot enter desktop expected less " +
-                                "than 3 focused tasks but found " + allFocusedTasks.size
+                        "DesktopTasksController: Cannot enter desktop, expected less " +
+                                "than 3 focused tasks but found %d",
+                        allFocusedTasks.size
                     )
                 }
             }
         }
     }
 
-    private fun findChildFocusedTask(allFocusedTasks: List<RunningTaskInfo>): RunningTaskInfo {
-        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) return allFocusedTasks[1]
-        return allFocusedTasks[0]
-    }
-
     /** Move a task with given `taskId` to desktop */
     fun moveToDesktop(
             taskId: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 269c369..0fea3e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,6 +35,7 @@
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
 
+import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks2;
@@ -51,6 +52,7 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -71,6 +73,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -79,6 +82,7 @@
  * Handles the global drag and drop handling for the Shell.
  */
 public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+        GlobalDragListener.GlobalDragListenerCallback,
         DisplayController.OnDisplaysChangedListener,
         View.OnDragListener, ComponentCallbacks2 {
 
@@ -90,6 +94,8 @@
     private final DisplayController mDisplayController;
     private final DragAndDropEventLogger mLogger;
     private final IconProvider mIconProvider;
+    private final GlobalDragListener mGlobalDragListener;
+    private final Transitions mTransitions;
     private SplitScreenController mSplitScreen;
     private ShellExecutor mMainExecutor;
     private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
@@ -112,6 +118,8 @@
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
+            GlobalDragListener globalDragListener,
+            Transitions transitions,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellController = shellController;
@@ -119,6 +127,8 @@
         mDisplayController = displayController;
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
+        mGlobalDragListener = globalDragListener;
+        mTransitions = transitions;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -136,6 +146,7 @@
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mGlobalDragListener.setListener(this);
     }
 
     private ExternalInterfaceBinder createExternalInterface() {
@@ -322,6 +333,14 @@
         return true;
     }
 
+    @Override
+    public void onCrossWindowDrop(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        // Bring the task forward when an item is dropped on it
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reorder(taskInfo.token, true /* onTop */);
+        mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null);
+    }
+
     /**
      * Handles dropping on the drop target.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
similarity index 67%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
index ccf48d0..7f7cfb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -15,12 +15,13 @@
  */
 package com.android.wm.shell.draganddrop
 
+import android.app.ActivityManager
 import android.os.RemoteException
 import android.util.Log
 import android.view.DragEvent
 import android.view.IWindowManager
+import android.window.IGlobalDragListener
 import android.window.IUnhandledDragCallback
-import android.window.IUnhandledDragListener
 import androidx.annotation.VisibleForTesting
 import com.android.internal.protolog.common.ProtoLog
 import com.android.wm.shell.common.ShellExecutor
@@ -30,25 +31,36 @@
 /**
  * Manages the listener and callbacks for unhandled global drags.
  */
-class UnhandledDragController(
-    val wmService: IWindowManager,
-    mainExecutor: ShellExecutor
+class GlobalDragListener(
+    private val wmService: IWindowManager,
+    private val mainExecutor: ShellExecutor
 ) {
-    private var callback: UnhandledDragAndDropCallback? = null
+    private var callback: GlobalDragListenerCallback? = null
 
-    private val unhandledDragListener: IUnhandledDragListener =
-        object : IUnhandledDragListener.Stub() {
+    private val globalDragListener: IGlobalDragListener =
+        object : IGlobalDragListener.Stub() {
+            override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+                mainExecutor.execute() {
+                    this@GlobalDragListener.onCrossWindowDrop(taskInfo)
+                }
+            }
+
             override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
                 mainExecutor.execute() {
-                    this@UnhandledDragController.onUnhandledDrop(event, callback)
+                    this@GlobalDragListener.onUnhandledDrop(event, callback)
                 }
             }
         }
 
     /**
-     * Listener called when an unhandled drag is started.
+     * Callbacks for global drag events.
      */
-    interface UnhandledDragAndDropCallback {
+    interface GlobalDragListenerCallback {
+        /**
+         * Called when a global drag is successfully handled by another window.
+         */
+        fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {}
+
         /**
          * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
          * dropped on a window that does not want to handle it).
@@ -62,7 +74,7 @@
     /**
      * Sets a listener for callbacks when an unhandled drag happens.
      */
-    fun setListener(listener: UnhandledDragAndDropCallback?) {
+    fun setListener(listener: GlobalDragListenerCallback?) {
         val updateWm = (callback == null && listener != null)
                 || (callback != null && listener == null)
         callback = listener
@@ -71,8 +83,8 @@
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                     "%s unhandled drag listener",
                     if (callback != null) "Registering" else "Unregistering")
-                wmService.setUnhandledDragListener(
-                    if (callback != null) unhandledDragListener else null)
+                wmService.setGlobalDragListener(
+                    if (callback != null) globalDragListener else null)
             } catch (e: RemoteException) {
                 Log.e(TAG, "Failed to set unhandled drag listener")
             }
@@ -80,11 +92,19 @@
     }
 
     @VisibleForTesting
+    fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+            "onCrossWindowDrop: %s", taskInfo)
+        callback?.onCrossWindowDrop(taskInfo)
+    }
+
+    @VisibleForTesting
     fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
             "onUnhandledDrop: %s", dragEvent)
         if (callback == null) {
             wmCallback.notifyUnhandledDropComplete(false)
+            return
         }
 
         callback?.onUnhandledDrop(dragEvent) {
@@ -95,6 +115,6 @@
     }
 
     companion object {
-        private val TAG = UnhandledDragController::class.java.simpleName
+        private val TAG = GlobalDragListener::class.java.simpleName
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index d173175..32442f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -191,7 +191,7 @@
             try {
                 ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
                         new PictureInPictureUiState.Builder()
-                                .setEnteringPip(true)
+                                .setTransitioningToPip(true)
                                 .build());
             } catch (RemoteException | IllegalStateException e) {
                 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -210,7 +210,7 @@
             try {
                 ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
                         new PictureInPictureUiState.Builder()
-                                .setEnteringPip(false)
+                                .setTransitioningToPip(false)
                                 .build());
             } catch (RemoteException | IllegalStateException e) {
                 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4b12134..4684077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -48,7 +48,6 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.Pair;
-import android.util.Size;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
@@ -1042,22 +1041,7 @@
     /** Save the state to restore to on re-entry. */
     public void saveReentryState(Rect pipBounds) {
         float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
-
-        if (!mPipBoundsState.hasUserResizedPip()) {
-            mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
-            return;
-        }
-
-        Size reentrySize = new Size(pipBounds.width(), pipBounds.height());
-
-        // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on
-        // fallback to using the userResizeBounds if userResizeBounds are not empty
-        if (!mTouchHandler.getUserResizeBounds().isEmpty()) {
-            Rect userResizeBounds = mTouchHandler.getUserResizeBounds();
-            reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height());
-        }
-
-        mPipBoundsState.saveReentryState(reentrySize, snapFraction);
+        mPipBoundsState.saveReentryState(snapFraction);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 5e79681..8a0e797 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1170,7 +1170,11 @@
         mPendingTransitions.add(0, active);
     }
 
-    /** Start a new transition directly. */
+    /**
+     * Start a new transition directly.
+     * @param handler if null, the transition will be dispatched to the registered set of transition
+     *                handlers to be handled
+     */
     public IBinder startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
new file mode 100644
index 0000000..8c0a524
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test auto entering pip using a source rect hint.
+ *
+ * To run this test: `atest AutoEnterPipWithSourceRectHintTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app in full screen
+ *     Select "Auto-enter PiP" radio button
+ *     Press "Set SourceRectHint" to create a temporary view that is used as the source rect hint
+ *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. All assertions are inherited from [AutoEnterPipOnGoToHomeTest]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
+    AutoEnterPipOnGoToHomeTest(flicker) {
+    override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+        setup {
+            pipApp.launchViaIntent(wmHelper)
+            pipApp.setSourceRectHint()
+            pipApp.enableAutoEnterForPipActivity()
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun pipOverlayNotShown() {
+        val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
+        flicker.assertLayers {
+            this.notContains(overlay)
+        }
+    }
+    @Presubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // we don't use overlay when entering with sourceRectHint
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+        // TODO (b/323511194): Looks like there is some bounciness with pip when using
+        // auto enter and sourceRectHint that causes the app to move outside of the display
+        // bounds during the transition.
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 54f36f6..a64ebd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -45,12 +45,14 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -84,7 +86,9 @@
     @Mock
     private ShellExecutor mMainExecutor;
     @Mock
-    private WindowManager mWindowManager;
+    private Transitions mTransitions;
+    @Mock
+    private GlobalDragListener mGlobalDragListener;
 
     private DragAndDropController mController;
 
@@ -93,7 +97,7 @@
         MockitoAnnotations.initMocks(this);
         mController = new DragAndDropController(mContext, mShellInit, mShellController,
                 mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
-                mMainExecutor);
+                mGlobalDragListener, mTransitions, mMainExecutor);
         mController.onInit();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
similarity index 65%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
index 522f052..e731b06 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.draganddrop
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.os.RemoteException
 import android.view.DragEvent
 import android.view.DragEvent.ACTION_DROP
@@ -25,19 +26,17 @@
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.draganddrop.UnhandledDragController.UnhandledDragAndDropCallback
+import com.android.wm.shell.draganddrop.GlobalDragListener.GlobalDragListenerCallback
 import java.util.function.Consumer
 import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
-import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
 
 /**
  * Tests for the unhandled drag controller.
@@ -45,35 +44,31 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UnhandledDragControllerTest : ShellTestCase() {
-    @Mock
-    private lateinit var mIWindowManager: IWindowManager
+    private val mIWindowManager = mock<IWindowManager>()
+    private val mMainExecutor = mock<ShellExecutor>()
 
-    @Mock
-    private lateinit var mMainExecutor: ShellExecutor
-
-    private lateinit var mController: UnhandledDragController
+    private lateinit var mController: GlobalDragListener
 
     @Before
     @Throws(RemoteException::class)
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mController = UnhandledDragController(mIWindowManager, mMainExecutor)
+        mController = GlobalDragListener(mIWindowManager, mMainExecutor)
     }
 
     @Test
     fun setListener_registersUnregistersWithWM() {
-        mController.setListener(object : UnhandledDragAndDropCallback {})
-        mController.setListener(object : UnhandledDragAndDropCallback {})
-        mController.setListener(object : UnhandledDragAndDropCallback {})
+        mController.setListener(object : GlobalDragListenerCallback {})
+        mController.setListener(object : GlobalDragListenerCallback {})
+        mController.setListener(object : GlobalDragListenerCallback {})
         verify(mIWindowManager, Mockito.times(1))
-                .setUnhandledDragListener(ArgumentMatchers.any())
+                .setGlobalDragListener(ArgumentMatchers.any())
 
         reset(mIWindowManager)
         mController.setListener(null)
         mController.setListener(null)
         mController.setListener(null)
         verify(mIWindowManager, Mockito.times(1))
-                .setUnhandledDragListener(ArgumentMatchers.isNull())
+                .setGlobalDragListener(ArgumentMatchers.isNull())
     }
 
     @Test
@@ -81,7 +76,7 @@
         // Simulate an unhandled drop
         val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
             null, null, false)
-        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        val wmCallback = mock<IUnhandledDragCallback>()
         mController.onUnhandledDrop(dropEvent, wmCallback)
 
         verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
@@ -92,7 +87,7 @@
         val lastDragEvent = arrayOfNulls<DragEvent>(1)
 
         // Set a listener to listen for unhandled drops
-        mController.setListener(object : UnhandledDragAndDropCallback {
+        mController.setListener(object : GlobalDragListenerCallback {
             override fun onUnhandledDrop(dragEvent: DragEvent,
                 onFinishedCallback: Consumer<Boolean>) {
                 lastDragEvent[0] = dragEvent
@@ -102,14 +97,31 @@
         })
 
         // Simulate an unhandled drop
-        val dragSurface = mock(SurfaceControl::class.java)
+        val dragSurface = mock<SurfaceControl>()
         val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
             dragSurface, null, false)
-        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        val wmCallback = mock<IUnhandledDragCallback>()
         mController.onUnhandledDrop(dropEvent, wmCallback)
 
         verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
         verify(dragSurface).release()
         assertEquals(lastDragEvent.get(0), dropEvent)
     }
+
+    @Test
+    fun onCrossWindowDrop() {
+        val lastTaskInfo = arrayOfNulls<RunningTaskInfo>(1)
+
+        // Set a listener to listen for unhandled drops
+        mController.setListener(object : GlobalDragListenerCallback {
+            override fun onCrossWindowDrop(taskInfo: RunningTaskInfo) {
+                lastTaskInfo[0] = taskInfo
+            }
+        })
+
+        // Simulate a cross-window drop
+        val taskInfo = mock<RunningTaskInfo>()
+        mController.onCrossWindowDrop(taskInfo)
+        assertEquals(lastTaskInfo.get(0), taskInfo)
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 46259a8..080b0ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -354,14 +354,17 @@
     }
 
     @Test
-    public void getEntryDestinationBounds_reentryStateExists_restoreLastSize() {
+    public void getEntryDestinationBounds_reentryStateExists_restoreProportionalSize() {
         mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
+        final Size maxSize = mSizeSpecSource.getMaxSize(DEFAULT_ASPECT_RATIO);
+        mPipBoundsState.setMaxSize(maxSize.getWidth(), maxSize.getHeight());
         final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         reentryBounds.scale(1.25f);
+        mPipBoundsState.setBounds(reentryBounds); // this updates the bounds scale used in reentry
+
         final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
 
-        mPipBoundsState.saveReentryState(
-                new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
+        mPipBoundsState.saveReentryState(reentrySnapFraction);
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
 
         assertEquals(reentryBounds.width(), destinationBounds.width());
@@ -375,8 +378,7 @@
         reentryBounds.offset(0, -100);
         final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
 
-        mPipBoundsState.saveReentryState(
-                new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
+        mPipBoundsState.saveReentryState(reentrySnapFraction);
 
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index db98abb..304da75f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -114,22 +114,19 @@
 
     @Test
     public void testSetReentryState() {
-        final Size size = new Size(100, 100);
         final float snapFraction = 0.5f;
 
-        mPipBoundsState.saveReentryState(size, snapFraction);
+        mPipBoundsState.saveReentryState(snapFraction);
 
         final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
-        assertEquals(size, state.getSize());
         assertEquals(snapFraction, state.getSnapFraction(), 0.01);
     }
 
     @Test
     public void testClearReentryState() {
-        final Size size = new Size(100, 100);
         final float snapFraction = 0.5f;
 
-        mPipBoundsState.saveReentryState(size, snapFraction);
+        mPipBoundsState.saveReentryState(snapFraction);
         mPipBoundsState.clearReentryState();
 
         assertNull(mPipBoundsState.getReentryState());
@@ -138,20 +135,19 @@
     @Test
     public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() {
         mPipBoundsState.setLastPipComponentName(mTestComponentName1);
-        mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
+        mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
 
         mPipBoundsState.setLastPipComponentName(mTestComponentName1);
 
         final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
         assertNotNull(state);
-        assertEquals(DEFAULT_SIZE, state.getSize());
         assertEquals(DEFAULT_SNAP_FRACTION, state.getSnapFraction(), 0.01);
     }
 
     @Test
     public void testSetLastPipComponentName_changed_clearReentryState() {
         mPipBoundsState.setLastPipComponentName(mTestComponentName1);
-        mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
+        mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
 
         mPipBoundsState.setLastPipComponentName(mTestComponentName2);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 4eb5193..5d968d3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -45,7 +45,6 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.Size;
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -256,40 +255,13 @@
     }
 
     @Test
-    public void saveReentryState_noUserResize_doesNotSaveSize() {
+    public void saveReentryState_savesPipBoundsState() {
         final Rect bounds = new Rect(0, 0, 10, 10);
         when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
-        when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(false);
 
         mPipController.saveReentryState(bounds);
 
-        verify(mMockPipBoundsState).saveReentryState(null, 1.0f);
-    }
-
-    @Test
-    public void saveReentryState_nonEmptyUserResizeBounds_savesSize() {
-        final Rect bounds = new Rect(0, 0, 10, 10);
-        final Rect resizedBounds = new Rect(0, 0, 30, 30);
-        when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
-        when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
-        when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
-
-        mPipController.saveReentryState(bounds);
-
-        verify(mMockPipBoundsState).saveReentryState(new Size(30, 30), 1.0f);
-    }
-
-    @Test
-    public void saveReentryState_emptyUserResizeBounds_savesSize() {
-        final Rect bounds = new Rect(0, 0, 10, 10);
-        final Rect resizedBounds = new Rect(0, 0, 0, 0);
-        when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
-        when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
-        when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
-
-        mPipController.saveReentryState(bounds);
-
-        verify(mMockPipBoundsState).saveReentryState(new Size(10, 10), 1.0f);
+        verify(mMockPipBoundsState).saveReentryState(1.0f);
     }
 
     @Test
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index b662901..0222782 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -697,6 +697,19 @@
         });
     }
 
+    private void onDisconnectRequested(ServiceCallbacks callback) {
+        mHandler.post(
+                () -> {
+                    Log.i(TAG, "onDisconnectRequest for " + mServiceComponent);
+
+                    if (!isCurrent(callback, "onDisconnectRequest")) {
+                        return;
+                    }
+                    forceCloseConnection();
+                    mCallback.onDisconnected();
+                });
+    }
+
     /**
      * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
      */
@@ -880,6 +893,19 @@
          */
         public void onConnectionFailed() {
         }
+
+        /**
+         * Invoked after disconnecting by request of the {@link MediaBrowserService}.
+         *
+         * <p>The default implementation of this method calls {@link #onConnectionFailed()}.
+         *
+         * @hide
+         */
+        // TODO: b/185136506 - Consider publishing this API in the next window for API changes, if
+        // the need arises.
+        public void onDisconnected() {
+            onConnectionFailed();
+        }
     }
 
     /**
@@ -1112,6 +1138,14 @@
                 mediaBrowser.onLoadChildren(this, parentId, list, options);
             }
         }
+
+        @Override
+        public void onDisconnect() {
+            MediaBrowser mediaBrowser = mMediaBrowser.get();
+            if (mediaBrowser != null) {
+                mediaBrowser.onDisconnectRequested(this);
+            }
+        }
     }
 
     private static class Subscription {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 8dba040..6cf9c6f 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -104,3 +104,10 @@
     description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
     bug: "281072508"
 }
+
+flag {
+    name: "enable_null_session_in_media_browser_service"
+    namespace: "media_solutions"
+    description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
+    bug: "263520343"
+}
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index ae0c2ab..45b4370 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -478,7 +478,7 @@
   &lt;intent-filter>
     &lt;action android:name="android.media.midi.MidiUmpDeviceService" />
   &lt;/intent-filter>
-  &lt;meta-data android:name="android.media.midi.MidiUmpDeviceService"
+  &lt;property android:name="android.media.midi.MidiUmpDeviceService"
       android:resource="@xml/<strong>echo_device_info</strong>" />
 &lt;/service>
 </pre>
diff --git a/media/java/android/media/tv/SignalingDataResponse.java b/media/java/android/media/tv/SignalingDataResponse.java
index be172ec..51fa6a2 100644
--- a/media/java/android/media/tv/SignalingDataResponse.java
+++ b/media/java/android/media/tv/SignalingDataResponse.java
@@ -73,6 +73,10 @@
     /**
      * Gets a list of types of metadata that are contained in this response.
      *
+     * <p> This list correlates to all the available types that can be found within
+     * {@link #getSignalingDataInfoList()}. This list is determined by the types specified in
+     * {@link SignalingDataRequest#getSignalingDataTypes()}.
+     *
      * <p> A list of types available are defined in {@link SignalingDataRequest}.
      * For more information about these types, see A/344:2023-5 9.2.10 - Query Signaling Data API.
      *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index f332f81..84d08db 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -964,7 +964,11 @@
 
         /**
          * Called when the TV App sends the selected track info as a response to
-         * {@link #requestSelectedTrackInfo()}
+         * {@link #requestSelectedTrackInfo()}.
+         *
+         * <p> When a selected track changes as a result of a new selection,
+         * {@link #onTrackSelected(int, String)} should be used instead to communicate the specific
+         * track selection.
          *
          * @param tracks A list of {@link TvTrackInfo} that are currently selected
          */
@@ -1383,6 +1387,8 @@
          * <p> Normally, track info cannot be synchronized until the channel has
          * been changed. This is used when the session of the {@link TvInteractiveAppService}
          * is newly created and the normal synchronization has not happened yet.
+         *
+         * <p> The track info will be returned in {@link #onSelectedTrackInfo(List)}
          */
         @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         @CallSuper
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 29a3b98..635572d 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -585,7 +585,8 @@
     }
 
     /**
-     * Sends the currently selected track info to the TV Interactive App.
+     * Sends the currently selected track info to the TV Interactive App in response to a
+     * {@link TvInteractiveAppCallback#onRequestSelectedTrackInfo(String)} request.
      *
      * @param tracks list of {@link TvTrackInfo} of the currently selected track(s)
      */
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index a877207..fbb7cfd 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -24,4 +24,11 @@
     @UnsupportedAppUsage
     void onConnectFailed();
     void onLoadChildren(String mediaId, in ParceledListSlice list, in Bundle options);
+    /**
+     * Invoked when the browser service cuts off the connection with the browser.
+     *
+     * <p>The browser must also clean up any state associated with this connection, as if the
+     * service had been destroyed.
+     */
+    void onDisconnect();
 }
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index d17b341..39ef528 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -38,10 +38,13 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.media.flags.Flags;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -51,6 +54,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Base class for media browser services.
@@ -96,6 +100,7 @@
 
     private static final int RESULT_ERROR = -1;
     private static final int RESULT_OK = 0;
+    private final ServiceBinder mBinder;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -105,7 +110,7 @@
 
     private final Handler mHandler = new Handler();
 
-    private final ServiceState mServiceState = new ServiceState();
+    private final AtomicReference<ServiceState> mServiceState;
 
     // Holds the connection record associated with the currently executing callback operation, if
     // any. See getCurrentBrowserInfo for an example. Must only be accessed on mHandler.
@@ -216,16 +221,21 @@
     }
 
     private static class ServiceBinder extends IMediaBrowserService.Stub {
-        private WeakReference<ServiceState> mServiceState;
+        private final AtomicReference<WeakReference<ServiceState>> mServiceState;
 
         private ServiceBinder(ServiceState serviceState) {
-            mServiceState = new WeakReference(serviceState);
+            mServiceState = new AtomicReference<>();
+            setServiceState(serviceState);
+        }
+
+        public void setServiceState(ServiceState serviceState) {
+            mServiceState.set(new WeakReference<>(serviceState));
         }
 
         @Override
         public void connect(final String pkg, final Bundle rootHints,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            ServiceState serviceState = mServiceState.get();
+            ServiceState serviceState = mServiceState.get().get();
             if (serviceState == null) {
                 return;
             }
@@ -243,7 +253,7 @@
 
         @Override
         public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
-            ServiceState serviceState = mServiceState.get();
+            ServiceState serviceState = mServiceState.get().get();
             if (serviceState == null) {
                 return;
             }
@@ -260,7 +270,7 @@
         @Override
         public void addSubscription(final String id, final IBinder token, final Bundle options,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            ServiceState serviceState = mServiceState.get();
+            ServiceState serviceState = mServiceState.get().get();
             if (serviceState == null) {
                 return;
             }
@@ -278,7 +288,7 @@
         @Override
         public void removeSubscription(final String id, final IBinder token,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            ServiceState serviceState = mServiceState.get();
+            ServiceState serviceState = mServiceState.get().get();
             if (serviceState == null) {
                 return;
             }
@@ -294,7 +304,7 @@
         @Override
         public void getMediaItem(final String mediaId, final ResultReceiver receiver,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            ServiceState serviceState = mServiceState.get();
+            ServiceState serviceState = mServiceState.get().get();
             if (serviceState == null) {
                 return;
             }
@@ -304,17 +314,23 @@
         }
     }
 
+    /** Default constructor. */
+    public MediaBrowserService() {
+        mServiceState = new AtomicReference<>(new ServiceState());
+        mBinder = new ServiceBinder(mServiceState.get());
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
-        mServiceState.mBinder = new ServiceBinder(mServiceState);
     }
 
     @Override
     public IBinder onBind(Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mServiceState.mBinder;
+            return mBinder;
         }
+
         return null;
     }
 
@@ -428,21 +444,33 @@
 
     /**
      * Call to set the media session.
-     * <p>
-     * This should be called as soon as possible during the service's startup.
-     * It may only be called once.
+     *
+     * <p>This should be called as soon as possible during the service's startup. It may only be
+     * called once.
      *
      * @param token The token for the service's {@link MediaSession}.
      */
+    // TODO: b/185136506 - Update the javadoc to reflect API changes when
+    // enableNullSessionInMediaBrowserService makes it to nextfood.
     public void setSessionToken(final MediaSession.Token token) {
+        ServiceState serviceState = mServiceState.get();
         if (token == null) {
-            throw new IllegalArgumentException("Session token may not be null.");
-        }
-        if (mServiceState.mSession != null) {
+            if (!Flags.enableNullSessionInMediaBrowserService()) {
+                throw new IllegalArgumentException("Session token may not be null.");
+            } else if (serviceState.mSession != null) {
+                ServiceState newServiceState = new ServiceState();
+                mBinder.setServiceState(newServiceState);
+                mServiceState.set(newServiceState);
+                serviceState.release();
+            } else {
+                // Nothing to do. The session is already null.
+            }
+        } else if (serviceState.mSession != null) {
             throw new IllegalStateException("The session token has already been set.");
+        } else {
+            serviceState.mSession = token;
+            mHandler.post(() -> serviceState.notifySessionTokenInitializedOnHandler(token));
         }
-        mServiceState.mSession = token;
-        mHandler.post(() -> mServiceState.notifySessionTokenInitializedOnHandler(token));
     }
 
     /**
@@ -450,7 +478,7 @@
      * or if it has been destroyed.
      */
     public @Nullable MediaSession.Token getSessionToken() {
-        return mServiceState.mSession;
+        return mServiceState.get().mSession;
     }
 
     /**
@@ -521,7 +549,7 @@
         if (parentId == null) {
             throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged");
         }
-        mHandler.post(() -> mServiceState.notifyChildrenChangeOnHandler(parentId, options));
+        mHandler.post(() -> mServiceState.get().notifyChildrenChangeOnHandler(parentId, options));
     }
 
     /**
@@ -623,15 +651,38 @@
 
         // Fields accessed from any caller thread.
         @Nullable private MediaSession.Token mSession;
-        @Nullable private ServiceBinder mBinder;
 
         // Fields accessed from mHandler only.
         @NonNull private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
 
+        public ServiceBinder getBinder() {
+            return mBinder;
+        }
+
         public void postOnHandler(Runnable runnable) {
             mHandler.post(runnable);
         }
 
+        public void release() {
+            mHandler.postAtFrontOfQueue(this::clearConnectionsOnHandler);
+        }
+
+        private void clearConnectionsOnHandler() {
+            Iterator<ConnectionRecord> iterator = mConnections.values().iterator();
+            while (iterator.hasNext()) {
+                ConnectionRecord record = iterator.next();
+                iterator.remove();
+                try {
+                    record.callbacks.onDisconnect();
+                } catch (RemoteException exception) {
+                    Log.w(
+                            TAG,
+                            TextUtils.formatSimple("onDisconnectRequest for %s failed", record.pkg),
+                            exception);
+                }
+            }
+        }
+
         public void removeConnectionRecordOnHandler(IMediaBrowserServiceCallbacks callbacks) {
             IBinder b = callbacks.asBinder();
             // Clear out the old subscriptions. We are getting new ones.
@@ -796,8 +847,7 @@
                         @Override
                         void onResultSent(
                                 List<MediaBrowser.MediaItem> list, @ResultFlags int flag) {
-                            if (mServiceState.mConnections.get(connection.callbacks.asBinder())
-                                    != connection) {
+                            if (mConnections.get(connection.callbacks.asBinder()) != connection) {
                                 if (DBG) {
                                     Log.d(
                                             TAG,
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 6efb028..4708e69 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -87,7 +87,7 @@
 
 const AInputEvent* AKeyEvent_fromJava(JNIEnv* env, jobject keyEvent) {
     std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
-    *event = android::android_view_KeyEvent_toNative(env, keyEvent);
+    *event = android::android_view_KeyEvent_obtainAsCopy(env, keyEvent);
     return event.release();
 }
 
@@ -323,9 +323,9 @@
                                                                   static_cast<const MotionEvent&>(
                                                                           *aInputEvent));
         case AINPUT_EVENT_TYPE_KEY:
-            return android::android_view_KeyEvent_fromNative(env,
-                                                             static_cast<const KeyEvent&>(
-                                                                     *aInputEvent));
+            return android::android_view_KeyEvent_obtainAsCopy(env,
+                                                               static_cast<const KeyEvent&>(
+                                                                       *aInputEvent));
         default:
             LOG_ALWAYS_FATAL("Unexpected event type %d in AInputEvent_toJava.", eventType);
     }
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index f111327..e244520 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -228,20 +228,10 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onDeactivated(int);
     method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
-    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
     method public final void sendResponseApdu(byte[]);
     field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
     field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
     field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
     field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
   }
@@ -274,6 +264,23 @@
     field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
   }
 
+  @FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable {
+    ctor public PollingFrame(int, @Nullable byte[], int, int);
+    method public int describeContents();
+    method @NonNull public byte[] getData();
+    method public int getGain();
+    method public int getTimestamp();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_B = 66; // 0x42
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_F = 70; // 0x46
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_OFF = 88; // 0x58
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_ON = 79; // 0x4f
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x55
+  }
+
 }
 
 package android.nfc.tech {
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index c5b7582..e5752d1 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.nfc.cardemulation.PollingFrame;
 import android.nfc.tech.MifareClassic;
 import android.nfc.tech.Ndef;
 import android.nfc.tech.NfcA;
@@ -2799,7 +2800,8 @@
      */
     @TestApi
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public void notifyPollingLoop(@NonNull Bundle frame) {
+    public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
+        Bundle frame = pollingFrame.toBundle();
         try {
             if (sService == null) {
                 attemptDeadServiceRecovery(null);
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 363788e..61037a2 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,85 +244,6 @@
     public static final String KEY_DATA = "data";
 
     /**
-     * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
-     * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
-
-    /**
-     * POLLING_LOOP_TYPE_A is the value associated with the key
-     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the polling loop is for NFC-A.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final char POLLING_LOOP_TYPE_A = 'A';
-
-    /**
-     * POLLING_LOOP_TYPE_B is the value associated with the key
-     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the polling loop is for NFC-B.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final char POLLING_LOOP_TYPE_B = 'B';
-
-    /**
-     * POLLING_LOOP_TYPE_F is the value associated with the key
-     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the polling loop is for NFC-F.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final char POLLING_LOOP_TYPE_F = 'F';
-
-    /**
-     * POLLING_LOOP_TYPE_ON is the value associated with the key
-     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the polling loop turns on.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final char POLLING_LOOP_TYPE_ON = 'O';
-
-    /**
-     * POLLING_LOOP_TYPE_OFF is the value associated with the key
-     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the polling loop turns off.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final char POLLING_LOOP_TYPE_OFF = 'X';
-
-    /**
-     * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
-     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the polling loop frame isn't recognized.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
-
-    /**
-     * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
-     * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the frame type isn't recognized.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
-
-    /**
-     * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
-     * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the frame type isn't recognized.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
-
-    /**
-     * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
-     * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
-     * when the frame type isn't recognized.
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
-
-    /**
      * @hide
      */
     public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
@@ -407,7 +328,12 @@
                     ArrayList<Bundle> frames =
                             msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
                             Bundle.class);
-                    processPollingFrames(frames);
+                    ArrayList<PollingFrame> pollingFrames =
+                            new ArrayList<PollingFrame>(frames.size());
+                    for (Bundle frame : frames) {
+                        pollingFrames.add(new PollingFrame(frame));
+                    }
+                    processPollingFrames(pollingFrames);
                     break;
             default:
                 super.handleMessage(msg);
@@ -482,7 +408,7 @@
      * @param frame A description of the polling frame.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public void processPollingFrames(@NonNull List<Bundle> frame) {
+    public void processPollingFrames(@NonNull List<PollingFrame> frame) {
     }
 
     /**
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
new file mode 100644
index 0000000..3383f3b
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HexFormat;
+import java.util.List;
+
+/**
+ * Polling Frames represent data about individual frames of an NFC polling loop. These frames will
+ * be deliverd to subclasses of {@link HostApduService} that have registered filters with
+ * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String)} that match a
+ * given frame in a loop and will be delivered through calls to
+ * {@link HostApduService#processPollingFrames(List)}.
+ */
+@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+public final class PollingFrame implements Parcelable{
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, value = { POLLING_LOOP_TYPE_A, POLLING_LOOP_TYPE_B,
+            POLLING_LOOP_TYPE_F, POLLING_LOOP_TYPE_OFF, POLLING_LOOP_TYPE_ON })
+    @Retention(RetentionPolicy.SOURCE)
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public @interface PollingFrameType {}
+
+    /**
+     * POLLING_LOOP_TYPE_A is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+     * when the polling loop is for NFC-A.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final int POLLING_LOOP_TYPE_A = 'A';
+
+    /**
+     * POLLING_LOOP_TYPE_B is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+     * when the polling loop is for NFC-B.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final int POLLING_LOOP_TYPE_B = 'B';
+
+    /**
+     * POLLING_LOOP_TYPE_F is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+     * when the polling loop is for NFC-F.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final int POLLING_LOOP_TYPE_F = 'F';
+
+    /**
+     * POLLING_LOOP_TYPE_ON is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+     * when the polling loop turns on.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final int POLLING_LOOP_TYPE_ON = 'O';
+
+    /**
+     * POLLING_LOOP_TYPE_OFF is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+     * when the polling loop turns off.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final int POLLING_LOOP_TYPE_OFF = 'X';
+
+    /**
+     * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+     * when the polling loop frame isn't recognized.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final int POLLING_LOOP_TYPE_UNKNOWN = 'U';
+
+    /**
+     * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
+     * polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
+
+    /**
+     * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+     * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+
+    /**
+     * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
+     * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+
+    /**
+     * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
+     * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+
+
+    @PollingFrameType
+    private final int mType;
+    private final byte[] mData;
+    private final int mGain;
+    private final int mTimestamp;
+
+    public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
+            new Parcelable.Creator<>() {
+                @Override
+                public PollingFrame createFromParcel(Parcel source) {
+                    return new PollingFrame(source.readBundle());
+                }
+
+                @Override
+                public PollingFrame[] newArray(int size) {
+                    return new PollingFrame[size];
+                }
+            };
+
+    PollingFrame(Bundle frame) {
+        mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
+        byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
+        mData = (data == null) ? new byte[0] : data;
+        mGain = frame.getByte(KEY_POLLING_LOOP_GAIN);
+        mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
+    }
+
+    public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
+            int gain, int timestamp) {
+        mType = type;
+        mData = data == null ? new byte[0] : data;
+        mGain = gain;
+        mTimestamp = timestamp;
+    }
+
+    /**
+     * Returns the type of frame for this polling loop frame.
+     * The possible return values are:
+     * <ul>
+     *   <li>{@link POLLING_LOOP_TYPE_ON}</li>
+     *   <li>{@link POLLING_LOOP_TYPE_OFF}</li>
+     *   <li>{@link POLLING_LOOP_TYPE_A}</li>
+     *   <li>{@link POLLING_LOOP_TYPE_B}</li>
+     *   <li>{@link POLLING_LOOP_TYPE_F}</li>
+     * </ul>
+     */
+    public @PollingFrameType int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the raw data from the polling type frame.
+     */
+    public @NonNull byte[] getData() {
+        return mData;
+    }
+
+    /**
+     * Returns the gain representing the field strength of the NFC field when this polling loop
+     * frame was observed.
+     */
+    public int getGain() {
+        return mGain;
+    }
+
+    /**
+     * Returns the timestamp of when the polling loop frame was observed in milliseconds. These
+     * timestamps are relative and not absolute and should only be used fro comparing the timing of
+     * frames relative to each other.
+     * @return the timestamp in milliseconds
+     */
+    public int getTimestamp() {
+        return mTimestamp;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBundle(toBundle());
+    }
+
+    /**
+     *
+     * @hide
+     * @return a Bundle representing this frame
+     */
+    public Bundle toBundle() {
+        Bundle frame = new Bundle();
+        frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
+        frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain());
+        frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
+        frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
+        return frame;
+    }
+
+    @Override
+    public String toString() {
+        return "PollingFrame { Type: " + (char) getType()
+                + ", gain: " + getGain()
+                + ", timestamp: " + Integer.toUnsignedString(getTimestamp())
+                + ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }";
+    }
+}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
index 6f70cfb..6902b6f 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
@@ -31,10 +31,7 @@
     fun updateRequest(intent: Intent)
 
     /** Sends an error encountered during the UI. */
-    fun sendError(
-        @BaseDialogResult.ResultCode resultCode: Int,
-        errorMessage: String? = null,
-    )
+    fun sendError(@BaseDialogResult.ResultCode resultCode: Int)
 
     /**
      * Sends a response to the system service. The response
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index 7f59ba3..ab70394 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -48,9 +48,13 @@
 
 
     override fun updateRequest(intent: Intent) {
-        val request = intent.parse(
-            context = context,
-        )
+        val request: Request
+        try {
+            request = intent.parse(context)
+        } catch (e: Exception) {
+            sendError(BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE)
+            return
+        }
         Log.d(TAG, "Request parsed: $request, client instance: $this")
         if (request is Request.Cancel || request is Request.Close) {
             if (request.token != null && request.token != _requests.value?.token) {
@@ -61,8 +65,9 @@
         _requests.value = request
     }
 
-    override fun sendError(resultCode: Int, errorMessage: String?) {
-        TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+    override fun sendError(resultCode: Int) {
+        Log.w(TAG, "Error occurred, resultCode: $resultCode, current request: ${ requests.value }")
+        requests.value?.sendCancellationCode(resultCode)
     }
 
     override fun sendResult(result: UserSelectionDialogResult) {
@@ -108,7 +113,7 @@
         return entryInfo.shouldTerminateUiUponSuccessfulProviderResult
     }
 
-    private fun Request.Get.sendCancellationCode(cancelCode: Int) {
+    private fun Request.sendCancellationCode(cancelCode: Int) {
         sendCancellationCode(
             cancelCode = cancelCode,
             requestToken = token,
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index 4a1c260..fd99275 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -25,6 +25,8 @@
  */
 sealed class Request private constructor(
     open val token: IBinder?,
+    open val resultReceiver: ResultReceiver? = null,
+    open val finalResponseReceiver: ResultReceiver? = null,
 ) {
 
     /**
@@ -48,10 +50,10 @@
      */
     data class Get(
         override val token: IBinder?,
-        val resultReceiver: ResultReceiver?,
-        val finalResponseReceiver: ResultReceiver?,
+        override val resultReceiver: ResultReceiver?,
+        override val finalResponseReceiver: ResultReceiver?,
         val providerInfos: List<ProviderInfo>,
-    ) : Request(token)
+    ) : Request(token, resultReceiver, finalResponseReceiver)
     /**
      * Request to start the create credentials flow.
      */
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
index 6bd166e..8c5c085 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
@@ -19,5 +19,6 @@
 import android.app.Application
 import dagger.hilt.android.HiltAndroidApp
 
+/** [Application] of credential selector. */
 @HiltAndroidApp(Application::class)
 class CredentialSelectorApp : Hilt_CredentialSelectorApp()
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index bfe80c4..463c4d1 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.credentialmanager
 
 import android.content.Intent
+import android.credentials.selection.BaseDialogResult
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.android.credentialmanager.CredentialSelectorUiState.Get
@@ -28,6 +29,10 @@
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.mappers.toGet
 import android.util.Log
+import com.android.credentialmanager.CredentialSelectorUiState.Cancel
+import com.android.credentialmanager.CredentialSelectorUiState.Close
+import com.android.credentialmanager.CredentialSelectorUiState.Create
+import com.android.credentialmanager.CredentialSelectorUiState.Idle
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -50,21 +55,21 @@
         ) { request, isPrimary, shouldClose ->
             if (shouldClose) {
                 Log.d(TAG, "Request finished, closing ")
-                return@combine CredentialSelectorUiState.Close
+                return@combine Close
             }
 
             when (request) {
-                null -> CredentialSelectorUiState.Idle
-                is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
-                is Request.Close -> CredentialSelectorUiState.Close
-                is Request.Create -> CredentialSelectorUiState.Create
+                null -> Idle
+                is Request.Cancel -> Cancel(request.appName)
+                is Request.Close -> Close
+                is Request.Create -> Create
                 is Request.Get -> request.toGet(isPrimary)
             }
         }
         .stateIn(
             viewModelScope,
             started = SharingStarted.WhileSubscribed(5000),
-            initialValue = CredentialSelectorUiState.Idle,
+            initialValue = Idle,
         )
 
     fun updateRequest(intent: Intent) {
@@ -75,16 +80,14 @@
         Log.d(TAG, "OnBackPressed")
         when (uiState.value) {
             is Get.MultipleEntry -> isPrimaryScreen.value = true
-            else -> {
-                shouldClose.value = true
-                // TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
-            }
+            is Create, Close, is Cancel, Idle -> shouldClose.value = true
+            is Get.SingleEntry, is Get.SingleEntryPerAccount -> cancel()
         }
     }
 
     override fun cancel() {
+        credentialManagerClient.sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
         shouldClose.value = true
-        // TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
     }
 
     override fun openSecondaryScreen() {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 332b816..f9a58871 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -120,7 +120,6 @@
         }
 
         is CredentialSelectorUiState.Cancel -> {
-            // TODO: b/300422310 - Implement cancel with message flow
             onCloseApp()
         }
 
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 5b39f4e..18e8fc3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -219,7 +219,6 @@
         }
     }
 
-
     /**
      * Shows restricted setting dialog.
      *
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 460a6f7..e185367 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -28,7 +28,7 @@
 import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
 import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
-import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuCheckBoxProvider
+import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
 import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
@@ -100,7 +100,7 @@
                 EditorMainPageProvider,
                 SettingsOutlinedTextFieldPageProvider,
                 SettingsExposedDropdownMenuBoxPageProvider,
-                SettingsExposedDropdownMenuCheckBoxProvider,
+                SettingsDropdownCheckBoxProvider,
                 SettingsTextFieldPasswordPageProvider,
                 SearchScaffoldPageProvider,
                 SuwScaffoldPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index 4875ea9..9f2158a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -37,7 +37,7 @@
                 .build(),
             SettingsExposedDropdownMenuBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
                 .build(),
-            SettingsExposedDropdownMenuCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
+            SettingsDropdownCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
                 .build(),
             SettingsTextFieldPasswordPageProvider.buildInjectEntry().setLink(fromPage = owner)
                 .build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt
new file mode 100644
index 0000000..33ab75d
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.settingslib.spa.gallery.editor
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox
+import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val TITLE = "Sample SettingsDropdownCheckBox"
+
+object SettingsDropdownCheckBoxProvider : SettingsPageProvider {
+    override val name = "SettingsDropdownCheckBox"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(title = TITLE) {
+            SettingsDropdownCheckBox(
+                label = "SettingsDropdownCheckBox",
+                options = remember {
+                    listOf(
+                        SettingsDropdownCheckOption("Item 1"),
+                        SettingsDropdownCheckOption("Item 2"),
+                        SettingsDropdownCheckOption("Item 3"),
+                    )
+                },
+            )
+            SettingsDropdownCheckBox(
+                label = "Empty list",
+                options = emptyList(),
+            )
+            SettingsDropdownCheckBox(
+                label = "Disabled",
+                options = remember {
+                    listOf(
+                        SettingsDropdownCheckOption("Item 1", selected = mutableStateOf(true)),
+                        SettingsDropdownCheckOption("Item 2"),
+                        SettingsDropdownCheckOption("Item 3"),
+                    )
+                },
+                enabled = false,
+            )
+            SettingsDropdownCheckBox(
+                label = "With disabled item",
+                options = remember {
+                    listOf(
+                        SettingsDropdownCheckOption("Item 1"),
+                        SettingsDropdownCheckOption("Item 2"),
+                        SettingsDropdownCheckOption(
+                            text = "Disabled item 1",
+                            changeable = false,
+                            selected = mutableStateOf(true),
+                        ),
+                        SettingsDropdownCheckOption("Disabled item 2", changeable = false),
+                    )
+                },
+            )
+            SettingsDropdownCheckBox(
+                label = "With select all",
+                options = remember {
+                    listOf(
+                        SettingsDropdownCheckOption("All", isSelectAll = true),
+                        SettingsDropdownCheckOption("Item 1"),
+                        SettingsDropdownCheckOption("Item 2"),
+                        SettingsDropdownCheckOption("Item 3"),
+                    )
+                },
+            )
+            SettingsDropdownCheckBox(
+                label = "With disabled item and select all",
+                options =
+                remember {
+                    listOf(
+                        SettingsDropdownCheckOption("All", isSelectAll = true, changeable = false),
+                        SettingsDropdownCheckOption("Item 1"),
+                        SettingsDropdownCheckOption("Item 2"),
+                        SettingsDropdownCheckOption(
+                            text = "Disabled item 1",
+                            changeable = false,
+                            selected = mutableStateOf(true),
+                        ),
+                        SettingsDropdownCheckOption("Disabled item 2", changeable = false),
+                    )
+                },
+            )
+        }
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage()).setUiLayoutFn {
+            Preference(object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
+            })
+        }
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsDropdownCheckBoxPagePreview() {
+    SettingsTheme {
+        SettingsDropdownCheckBoxProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt
deleted file mode 100644
index d289646..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.editor
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-
-private const val TITLE = "Sample SettingsExposedDropdownMenuCheckBox"
-
-object SettingsExposedDropdownMenuCheckBoxProvider : SettingsPageProvider {
-    override val name = "SettingsExposedDropdownMenuCheckBox"
-    private const val exposedDropdownMenuCheckBoxLabel = "ExposedDropdownMenuCheckBoxLabel"
-    private val options = listOf("item1", "item2", "item3")
-    private val selectedOptionsState1 = mutableStateListOf(0, 1)
-
-    override fun getTitle(arguments: Bundle?): String {
-        return TITLE
-    }
-
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            SettingsExposedDropdownMenuCheckBox(
-                label = exposedDropdownMenuCheckBoxLabel,
-                options = options,
-                selectedOptionsState = remember { selectedOptionsState1 },
-                enabled = true,
-                onSelectedOptionStateChange = {},
-            )
-        }
-    }
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = createSettingsPage()).setUiLayoutFn {
-            Preference(object : PreferenceModel {
-                override val title = TITLE
-                override val onClick = navigator(name)
-            })
-        }
-    }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun SettingsExposedDropdownMenuCheckBoxPagePreview() {
-    SettingsTheme {
-        SettingsExposedDropdownMenuCheckBoxProvider.Page(null)
-    }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
new file mode 100644
index 0000000..57963e6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption.Companion.changeable
+
+data class SettingsDropdownCheckOption(
+    /** The displayed text of this option. */
+    val text: String,
+
+    /** If true, check / uncheck this item will check / uncheck all enabled options. */
+    val isSelectAll: Boolean = false,
+
+    /** If not changeable, cannot check or uncheck this option. */
+    val changeable: Boolean = true,
+
+    /** The selected state of this option. */
+    val selected: MutableState<Boolean> = mutableStateOf(false),
+
+    /** Get called when the option is clicked, no matter if it's changeable. */
+    val onClick: () -> Unit = {},
+) {
+    companion object {
+        val List<SettingsDropdownCheckOption>.changeable: Boolean
+            get() = filter { !it.isSelectAll }.any { it.changeable }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsDropdownCheckBox(
+    label: String,
+    options: List<SettingsDropdownCheckOption>,
+    emptyText: String = "",
+    enabled: Boolean = true,
+    errorMessage: String? = null,
+    onSelectedStateChange: () -> Unit = {},
+) {
+    var dropDownWidth by remember { mutableIntStateOf(0) }
+    var expanded by remember { mutableStateOf(false) }
+    val changeable = enabled && options.changeable
+    ExposedDropdownMenuBox(
+        expanded = expanded,
+        onExpandedChange = { expanded = changeable && it },
+        modifier = Modifier
+            .width(350.dp)
+            .padding(SettingsDimension.textFieldPadding)
+            .onSizeChanged { dropDownWidth = it.width },
+    ) {
+        OutlinedTextField(
+            // The `menuAnchor` modifier must be passed to the text field for correctness.
+            modifier = Modifier
+                .menuAnchor()
+                .fillMaxWidth(),
+            value = getDisplayText(options) ?: emptyText,
+            onValueChange = {},
+            label = { Text(text = label) },
+            trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
+            readOnly = true,
+            enabled = changeable,
+            isError = errorMessage != null,
+            supportingText = errorMessage?.let { { Text(text = it) } },
+        )
+        ExposedDropdownMenu(
+            expanded = expanded,
+            modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
+            onDismissRequest = { expanded = false },
+        ) {
+            for (option in options) {
+                CheckboxItem(option) {
+                    option.onClick()
+                    if (option.changeable) {
+                        checkboxItemOnClick(options, option)
+                        onSelectedStateChange()
+                    }
+                }
+            }
+        }
+    }
+}
+
+private fun getDisplayText(options: List<SettingsDropdownCheckOption>): String? {
+    val selectedOptions = options.filter { it.selected.value }
+    if (selectedOptions.isEmpty()) return null
+    return selectedOptions.filter { it.isSelectAll }.ifEmpty { selectedOptions }
+        .joinToString { it.text }
+}
+
+private fun checkboxItemOnClick(
+    options: List<SettingsDropdownCheckOption>,
+    clickedOption: SettingsDropdownCheckOption,
+) {
+    if (!clickedOption.changeable) return
+    val newChecked = !clickedOption.selected.value
+    if (clickedOption.isSelectAll) {
+        for (option in options.filter { it.changeable }) option.selected.value = newChecked
+    } else {
+        clickedOption.selected.value = newChecked
+    }
+    val (selectAllOptions, regularOptions) = options.partition { it.isSelectAll }
+    val isAllRegularOptionsChecked = regularOptions.all { it.selected.value }
+    selectAllOptions.forEach { it.selected.value = isAllRegularOptionsChecked }
+}
+
+@Composable
+private fun CheckboxItem(
+    option: SettingsDropdownCheckOption,
+    onClick: (SettingsDropdownCheckOption) -> Unit,
+) {
+    TextButton(
+        onClick = { onClick(option) },
+        modifier = Modifier.fillMaxWidth(),
+    ) {
+        Row(
+            modifier = Modifier.fillMaxWidth(),
+            horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Checkbox(
+                checked = option.selected.value,
+                onCheckedChange = null,
+                enabled = option.changeable,
+            )
+            Text(text = option.text, modifier = Modifier.alphaForEnabled(option.changeable))
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun ActionButtonsPreview() {
+    val item1 = SettingsDropdownCheckOption("item1")
+    val item2 = SettingsDropdownCheckOption("item2")
+    val item3 = SettingsDropdownCheckOption("item3")
+    val options = listOf(item1, item2, item3)
+    SettingsTheme {
+        SettingsDropdownCheckBox(
+            label = "label",
+            options = options,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
deleted file mode 100644
index e704505..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.widget.editor
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.SnapshotStateList
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun SettingsExposedDropdownMenuCheckBox(
-    label: String,
-    options: List<String>,
-    selectedOptionsState: SnapshotStateList<Int>,
-    emptyVal: String = "",
-    enabled: Boolean,
-    errorMessage: String? = null,
-    onSelectedOptionStateChange: () -> Unit,
-) {
-    var dropDownWidth by remember { mutableIntStateOf(0) }
-    var expanded by remember { mutableStateOf(false) }
-    val allIndex = options.indexOf("*")
-    ExposedDropdownMenuBox(
-        expanded = expanded,
-        onExpandedChange = { expanded = it },
-        modifier = Modifier
-            .width(350.dp)
-            .padding(SettingsDimension.textFieldPadding)
-            .onSizeChanged { dropDownWidth = it.width },
-    ) {
-        OutlinedTextField(
-            // The `menuAnchor` modifier must be passed to the text field for correctness.
-            modifier = Modifier
-                .menuAnchor()
-                .fillMaxWidth(),
-            value = if (selectedOptionsState.size == 0) emptyVal
-            else if (selectedOptionsState.contains(allIndex)) "*"
-            else selectedOptionsState.joinToString { options[it] },
-            onValueChange = {},
-            label = { Text(text = label) },
-            trailingIcon = {
-                ExposedDropdownMenuDefaults.TrailingIcon(
-                    expanded = expanded
-                )
-            },
-            readOnly = true,
-            enabled = enabled,
-            isError = errorMessage != null,
-            supportingText = {
-                if (errorMessage != null) {
-                    Text(text = errorMessage)
-                }
-            }
-        )
-        if (options.isNotEmpty()) {
-            ExposedDropdownMenu(
-                expanded = expanded,
-                modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
-                onDismissRequest = { expanded = false },
-            ) {
-                options.forEachIndexed { index, option ->
-                    CheckboxItem(
-                        selectedOptionsState,
-                        index,
-                        allIndex,
-                        onSelectedOptionStateChange,
-                        option,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-private fun CheckboxItem(
-    selectedOptionsState: SnapshotStateList<Int>,
-    index: Int,
-    allIndex: Int,
-    onSelectedOptionStateChange: () -> Unit,
-    option: String
-) {
-    TextButton(
-        modifier = Modifier.fillMaxWidth(),
-        onClick = {
-            if (selectedOptionsState.contains(index)) {
-                if (index == allIndex) {
-                    selectedOptionsState.clear()
-                } else {
-                    selectedOptionsState.remove(index)
-                    selectedOptionsState.remove(allIndex)
-                }
-            } else {
-                selectedOptionsState.add(index)
-            }
-            onSelectedOptionStateChange()
-        }) {
-        Row(
-            modifier = Modifier.fillMaxWidth(),
-            horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
-            verticalAlignment = Alignment.CenterVertically
-        ) {
-            Checkbox(
-                checked = selectedOptionsState.contains(index),
-                onCheckedChange = null,
-            )
-            Text(text = option)
-        }
-    }
-}
-
-@Preview
-@Composable
-private fun ActionButtonsPreview() {
-    val options = listOf("item1", "item2", "item3")
-    val selectedOptionsState = remember { mutableStateListOf(0, 1) }
-    SettingsTheme {
-        SettingsExposedDropdownMenuCheckBox(
-            label = "label",
-            options = options,
-            selectedOptionsState = selectedOptionsState,
-            enabled = true,
-            onSelectedOptionStateChange = {})
-    }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt
new file mode 100644
index 0000000..72b7b98
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.settingslib.spa.widget.editor
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isPopup
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.hasRole
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDropdownCheckBoxTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun dropdownCheckBox_displayed() {
+        val item1 = SettingsDropdownCheckOption("item1")
+        val item2 = SettingsDropdownCheckOption("item2")
+        val item3 = SettingsDropdownCheckOption("item3")
+        composeTestRule.setContent {
+            SettingsDropdownCheckBox(
+                label = LABEL,
+                options = listOf(item1, item2, item3),
+            )
+        }
+
+        composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+    }
+
+    @Test
+    fun dropdownCheckBox_expanded() {
+        val item1 = SettingsDropdownCheckOption("item1")
+        val item2 = SettingsDropdownCheckOption("item2")
+        val item3 = SettingsDropdownCheckOption("item3")
+        composeTestRule.setContent {
+            SettingsDropdownCheckBox(
+                label = LABEL,
+                options = listOf(item1, item2, item3),
+            )
+        }
+        composeTestRule.onOption(item3).assertDoesNotExist()
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+
+        composeTestRule.onOption(item3).assertIsDisplayed()
+    }
+
+    @Test
+    fun dropdownCheckBox_valueAdded() {
+        val item1 = SettingsDropdownCheckOption("item1")
+        val item2 = SettingsDropdownCheckOption("item2")
+        val item3 = SettingsDropdownCheckOption("item3")
+        composeTestRule.setContent {
+            SettingsDropdownCheckBox(
+                label = LABEL,
+                options = listOf(item1, item2, item3),
+            )
+        }
+        composeTestRule.onDropdownBox(item3.text).assertDoesNotExist()
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+        composeTestRule.onOption(item3).performClick()
+
+        composeTestRule.onDropdownBox(item3.text).assertIsDisplayed()
+        assertThat(item3.selected.value).isTrue()
+    }
+
+    @Test
+    fun dropdownCheckBox_valueDeleted() {
+        val item1 = SettingsDropdownCheckOption("item1")
+        val item2 = SettingsDropdownCheckOption("item2", selected = mutableStateOf(true))
+        val item3 = SettingsDropdownCheckOption("item3")
+        composeTestRule.setContent {
+            SettingsDropdownCheckBox(
+                label = LABEL,
+                options = listOf(item1, item2, item3),
+            )
+        }
+        composeTestRule.onDropdownBox(item2.text).assertIsDisplayed()
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+        composeTestRule.onOption(item2).performClick()
+
+        composeTestRule.onDropdownBox(item2.text).assertDoesNotExist()
+        assertThat(item2.selected.value).isFalse()
+    }
+
+    @Test
+    fun dropdownCheckBox_withSelectAll() {
+        val selectAll = SettingsDropdownCheckOption("All", isSelectAll = true)
+        val item1 = SettingsDropdownCheckOption("item1")
+        val item2 = SettingsDropdownCheckOption("item2")
+        composeTestRule.setContent {
+            SettingsDropdownCheckBox(
+                label = LABEL,
+                options = listOf(selectAll, item1, item2),
+            )
+        }
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+        composeTestRule.onOption(selectAll).performClick()
+
+        composeTestRule.onDropdownBox(selectAll.text).assertIsDisplayed()
+        composeTestRule.onDropdownBox(item1.text).assertDoesNotExist()
+        composeTestRule.onDropdownBox(item2.text).assertDoesNotExist()
+        assertThat(item1.selected.value).isTrue()
+        assertThat(item2.selected.value).isTrue()
+    }
+
+    private companion object {
+        const val LABEL = "Label"
+    }
+}
+
+private fun ComposeContentTestRule.onDropdownBox(text: String) =
+    onNode(hasRole(Role.DropdownList) and hasText(text))
+
+private fun ComposeContentTestRule.onOption(option: SettingsDropdownCheckOption) =
+    onNode(hasAnyAncestor(isPopup()) and hasText(option.text))
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt
deleted file mode 100644
index 2b78ed7..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.widget.editor
-
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.isFocused
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsExposedDropdownMenuCheckBoxTest {
-    @get:Rule
-    val composeTestRule = createComposeRule()
-    private val item1 = "item1"
-    private val item2 = "item2"
-    private val item3 = "item3"
-    private val options = listOf(item1, item2, item3)
-    private val selectedOptionsState1 = mutableStateListOf(0, 1)
-    private val exposedDropdownMenuCheckBoxLabel = "ExposedDropdownMenuCheckBoxLabel"
-
-    @Test
-    fun exposedDropdownMenuCheckBox_displayed() {
-        composeTestRule.setContent {
-            SettingsExposedDropdownMenuCheckBox(
-                label = exposedDropdownMenuCheckBoxLabel,
-                options = options,
-                selectedOptionsState = remember { selectedOptionsState1 },
-                enabled = true,
-            ) {}
-        }
-        composeTestRule.onNodeWithText(
-            exposedDropdownMenuCheckBoxLabel, substring = true
-        ).assertIsDisplayed()
-    }
-
-    @Test
-    fun exposedDropdownMenuCheckBox_expanded() {
-        composeTestRule.setContent {
-            SettingsExposedDropdownMenuCheckBox(
-                label = exposedDropdownMenuCheckBoxLabel,
-                options = options,
-                selectedOptionsState = remember { selectedOptionsState1 },
-                enabled = true,
-            ) {}
-        }
-        composeTestRule.onNodeWithText(item3, substring = true).assertDoesNotExist()
-        composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true)
-            .performClick()
-        composeTestRule.onNodeWithText(item3, substring = true).assertIsDisplayed()
-    }
-
-    @Test
-    fun exposedDropdownMenuCheckBox_valueAdded() {
-        composeTestRule.setContent {
-            SettingsExposedDropdownMenuCheckBox(
-                label = exposedDropdownMenuCheckBoxLabel,
-                options = options,
-                selectedOptionsState = remember { selectedOptionsState1 },
-                enabled = true,
-            ) {}
-        }
-        composeTestRule.onNodeWithText(item3, substring = true).assertDoesNotExist()
-        composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true)
-            .performClick()
-        composeTestRule.onNodeWithText(item3, substring = true).performClick()
-        composeTestRule.onFocusedText(item3).assertIsDisplayed()
-    }
-
-    @Test
-    fun exposedDropdownMenuCheckBox_valueDeleted() {
-        composeTestRule.setContent {
-            SettingsExposedDropdownMenuCheckBox(
-                label = exposedDropdownMenuCheckBoxLabel,
-                options = options,
-                selectedOptionsState = remember { selectedOptionsState1 },
-                enabled = true,
-            ) {}
-        }
-        composeTestRule.onNodeWithText(item2, substring = true).assertIsDisplayed()
-        composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true)
-            .performClick()
-        composeTestRule.onNotFocusedText(item2).performClick()
-        composeTestRule.onFocusedText(item2).assertDoesNotExist()
-    }
-}
-
-fun ComposeContentTestRule.onFocusedText(text: String): SemanticsNodeInteraction =
-    onNode(isFocused() and hasText(text, substring = true))
-
-fun ComposeContentTestRule.onNotFocusedText(text: String): SemanticsNodeInteraction =
-    onNode(!isFocused() and hasText(text, substring = true))
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt
new file mode 100644
index 0000000..856bed6
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.settingslib.spa.testutils
+
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.SemanticsMatcher
+
+fun hasRole(role: Role) = SemanticsMatcher("${SemanticsProperties.Role.name} has $role") {
+    it.config.getOrNull(SemanticsProperties.Role) == role
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 9432d59..6b1893c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -31,7 +31,6 @@
 
 data class EnhancedConfirmation(
     val key: String,
-    val uid: Int,
     val packageName: String,
 )
 data class Restrictions(
@@ -91,7 +90,7 @@
         restrictions.enhancedConfirmation?.let { ec ->
             RestrictedLockUtilsInternal
                     .checkIfRequiresEnhancedConfirmation(context, ec.key,
-                        ec.uid, ec.packageName)
+                        ec.packageName)
                     ?.let { intent -> return BlockedByEcmImpl(context = context, intent = intent) }
         }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 74b556e..27e00c0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -159,7 +159,6 @@
             keys = switchRestrictionKeys,
             enhancedConfirmation = enhancedConfirmationKey?.let { EnhancedConfirmation(
                 key = it,
-                uid = checkNotNull(applicationInfo).uid,
                 packageName = packageName) })
         RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
         InfoPageAdditionalContent(record, isAllowed)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 4b47437..2e8b76a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -150,15 +150,13 @@
 
     @Composable
     fun getSummary(record: T): () -> String {
-        val restrictions = remember(record.app.userId,
-                record.app.uid, record.app.packageName) {
+        val restrictions = remember(record.app.userId, record.app.packageName) {
             Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
                 enhancedConfirmation = listModel.enhancedConfirmationKey?.let {
                     EnhancedConfirmation(
                         key = it,
-                        uid = record.app.uid,
                         packageName = record.app.packageName)
                 })
         }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
index 00ba9b4..b88d1c5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -32,6 +32,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
 import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -44,6 +45,7 @@
     val composeTestRule = createComposeRule()
 
     private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+    private val fakeBlockedByEcm = FakeBlockedByEcm()
 
     private val fakeRestrictionsProvider = FakeRestrictionsProvider()
 
@@ -141,6 +143,29 @@
         assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
     }
 
+    @Test
+    fun whenBlockedByEcm_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByEcm.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByEcm_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+    }
+
     private fun setContent(restrictions: Restrictions) {
         composeTestRule.setContent {
             RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 2ccf323..556adc7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -29,6 +29,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
 import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -41,6 +42,7 @@
     val composeTestRule = createComposeRule()
 
     private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+    private val fakeBlockedByEcm = FakeBlockedByEcm()
 
     private val fakeRestrictionsProvider = FakeRestrictionsProvider()
 
@@ -129,6 +131,28 @@
         assertThat(menuItemOnClickIsCalled).isFalse()
     }
 
+    @Test
+    fun whenBlockedByEcm_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByEcm_onClick_showEcmDetails() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+        assertThat(menuItemOnClickIsCalled).isFalse()
+    }
+
     private fun setContent(restrictions: Restrictions) {
         val fakeMoreOptionsScope = object : MoreOptionsScope() {
             override fun dismiss() {}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
index 93fa17d..f8ca2a0 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
 
@@ -36,6 +37,18 @@
     }
 }
 
+class FakeBlockedByEcm : BlockedByEcm {
+    var showRestrictedSettingsDetailsIsCalled = false
+
+    override fun showRestrictedSettingsDetails() {
+        showRestrictedSettingsDetailsIsCalled = true
+    }
+
+    companion object {
+        const val SUMMARY = "Disabled"
+    }
+}
+
 class FakeRestrictionsProvider : RestrictionsProvider {
     var restrictedMode: RestrictedMode? = null
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
new file mode 100644
index 0000000..6578eb7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.DropDownPreference;
+import androidx.preference.PreferenceViewHolder;
+
+public class RestrictedDropDownPreference extends DropDownPreference {
+    RestrictedPreferenceHelper mHelper;
+
+    public RestrictedDropDownPreference(@NonNull Context context) {
+        super(context);
+        mHelper = new RestrictedPreferenceHelper(context, this, null);
+    }
+
+    /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param settingIdentifier The key identifying the setting
+     * @param packageName the package to check the settingIdentifier for
+     */
+    public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+            @NonNull String packageName) {
+        mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        mHelper.onBindViewHolder(holder);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (enabled && isDisabledByEcm()) {
+            mHelper.setDisabledByEcm(null);
+            return;
+        }
+
+        super.setEnabled(enabled);
+    }
+
+    @Override
+    public void performClick() {
+        if (!mHelper.performClick()) {
+            super.performClick();
+        }
+    }
+
+    public boolean isDisabledByEcm() {
+        return mHelper.isDisabledByEcm();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index d902457..f36da19 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -23,11 +23,11 @@
 
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
 
-import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
 import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -42,12 +42,10 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManager.EnforcingUser;
-import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.ImageSpan;
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.MenuItem;
 import android.widget.TextView;
@@ -60,7 +58,6 @@
 import com.android.internal.widget.LockPatternUtils;
 
 import java.util.List;
-import java.util.Set;
 
 /**
  * Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -70,24 +67,11 @@
 
     private static final String LOG_TAG = "RestrictedLockUtils";
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
-    private static final Set<String> ECM_KEYS = new ArraySet<>();
 
     // TODO(b/281701062): reference role name from role manager once its exposed.
     private static final String ROLE_DEVICE_LOCK_CONTROLLER =
             "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
 
-    static {
-        if (android.security.Flags.extendEcmToAllSettings()) {
-            ECM_KEYS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
-            ECM_KEYS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
-            ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
-            ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN);
-        }
-
-        ECM_KEYS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
-        ECM_KEYS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
-    }
-
     /**
      * @return drawables for displaying with settings that are locked by a device admin.
      */
@@ -112,32 +96,63 @@
      */
     @Nullable
     public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context,
-                                                             @NonNull String restriction,
-                                                             int uid,
-                                                             @Nullable String packageName) {
-        // TODO(b/297372999): Replace with call to mainline module once ready
+            @NonNull String settingIdentifier, @NonNull String packageName) {
 
-        if (!ECM_KEYS.contains(restriction)) {
+        if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                || !android.security.Flags.extendEcmToAllSettings()) {
             return null;
         }
 
-        final AppOpsManager appOps = (AppOpsManager) context
-                .getSystemService(Context.APP_OPS_SERVICE);
-        final int mode = appOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
-                uid, packageName, null, null);
-        final boolean ecmEnabled = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
-        if (ecmEnabled && mode != AppOpsManager.MODE_ALLOWED) {
-            final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
-            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
-            intent.putExtra(Intent.EXTRA_UID, uid);
-            return intent;
+        EnhancedConfirmationManager ecManager = (EnhancedConfirmationManager) context
+                .getSystemService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE);
+        try {
+            if (ecManager.isRestricted(packageName, settingIdentifier)) {
+                return ecManager.createRestrictedSettingDialogIntent(
+                        packageName, settingIdentifier);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(LOG_TAG, "package not found: " + packageName, e);
         }
 
         return null;
     }
 
     /**
+     * <p>This is {@code true} when the setting is a protected setting (i.e., a sensitive resource),
+     * and the app is restricted (i.e., considered dangerous), and the user has not yet cleared the
+     * app's restriction status (i.e., by clicking "Allow restricted settings" for this app).     *
+     */
+    public static boolean isEnhancedConfirmationRestricted(@NonNull Context context,
+            @NonNull String settingIdentifier, @NonNull String packageName) {
+        if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                && android.security.Flags.extendEcmToAllSettings()) {
+            try {
+                return context.getSystemService(EnhancedConfirmationManager.class)
+                        .isRestricted(packageName, settingIdentifier);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
+                return false;
+            }
+        } else {
+            try {
+                if (!settingIdentifier.equals(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE)) {
+                    return false;
+                }
+                int uid = context.getPackageManager().getPackageUid(packageName, 0);
+                final int mode = context.getSystemService(AppOpsManager.class)
+                        .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                        uid, packageName);
+                final boolean ecmEnabled = context.getResources().getBoolean(
+                        com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+                return ecmEnabled && mode != AppOpsManager.MODE_ALLOWED;
+            } catch (Exception e) {
+                // Fallback in case if app ops is not available in testing.
+                return false;
+            }
+        }
+    }
+
+    /**
      * Checks if a restriction is enforced on a user and returns the enforced admin and
      * admin userId.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 50e3bd0..495410b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -23,6 +23,7 @@
 import android.os.UserHandle;
 import android.util.AttributeSet;
 
+import androidx.annotation.NonNull;
 import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceViewHolder;
@@ -99,12 +100,12 @@
     /**
      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
      * package. Marks the preference as disabled if so.
-     * @param restriction The key identifying the setting
-     * @param packageName the package to check the restriction for
-     * @param uid the uid of the package
+     * @param settingIdentifier The key identifying the setting
+     * @param packageName the package to check the settingIdentifier for
      */
-    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
-        mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+    public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+            @NonNull String packageName) {
+        mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index a479269..734b92c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -31,6 +31,8 @@
 import android.util.TypedValue;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -43,9 +45,17 @@
  * by device admins via user restrictions.
  */
 public class RestrictedPreferenceHelper {
+    private static final String TAG = "RestrictedPreferenceHelper";
+
     private final Context mContext;
     private final Preference mPreference;
     String packageName;
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
     int uid;
 
     private boolean mDisabledByAdmin;
@@ -148,14 +158,15 @@
             return true;
         }
         if (mDisabledByEcm) {
-            if (android.security.Flags.extendEcmToAllSettings()) {
+            if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                    && android.security.Flags.extendEcmToAllSettings()) {
                 mContext.startActivity(mDisabledByEcmIntent);
                 return true;
+            } else {
+                RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext,
+                        packageName, uid);
+                return true;
             }
-
-            RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
-                    uid);
-            return true;
         }
         return false;
     }
@@ -184,14 +195,14 @@
     /**
      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
      * package. Marks the preference as disabled if so.
-     * @param restriction The key identifying the setting
-     * @param packageName the package to check the restriction for
-     * @param uid the uid of the package
+     * @param settingIdentifier The key identifying the setting
+     * @param packageName the package to check the settingIdentifier for
      */
-    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
-        updatePackageDetails(packageName, uid);
+    public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+            @NonNull String packageName) {
+        updatePackageDetails(packageName, android.os.Process.INVALID_UID);
         Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation(
-                mContext, restriction, uid, packageName);
+                mContext, settingIdentifier, packageName);
         setDisabledByEcm(intent);
     }
 
@@ -240,7 +251,7 @@
      * be disabled.
      * @return true if the disabled state was changed.
      */
-    public boolean setDisabledByEcm(Intent disabledIntent) {
+    public boolean setDisabledByEcm(@Nullable Intent disabledIntent) {
         boolean disabled = disabledIntent != null;
         boolean changed = false;
         if (mDisabledByEcm != disabled) {
@@ -275,6 +286,10 @@
         if (mPreference instanceof PrimarySwitchPreference) {
             ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled);
         }
+
+        if (!isEnabled && mDisabledByEcm) {
+            mPreference.setSummary(R.string.disabled_by_app_ops_text);
+        }
     }
 
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 70ece0f..0c54c19 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -200,12 +200,12 @@
     /**
      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
      * package. Marks the preference as disabled if so.
-     * @param restriction The key identifying the setting
-     * @param packageName the package to check the restriction for
-     * @param uid the uid of the package
+     * @param settingIdentifier The key identifying the setting
+     * @param packageName the package to check the settingIdentifier for
      */
-    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
-        mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+    public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+            @NonNull  String packageName) {
+        mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index a376c1f..50e2f9c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -161,11 +161,6 @@
 
     protected abstract void startScanOnRouter();
 
-    /**
-     * Transfer MediaDevice for media without package name.
-     */
-    protected abstract boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device);
-
     protected abstract void transferToRoute(@NonNull MediaRoute2Info route);
 
     protected abstract void selectRoute(
@@ -199,6 +194,12 @@
     @NonNull
     protected abstract List<RoutingSessionInfo> getRemoteSessions();
 
+    /**
+     * Returns a non-empty list containing the routing sessions associated to the target media app.
+     *
+     * <p> The first item of the list is always the {@link RoutingSessionInfo#isSystemSession()
+     * system session}, followed other remote sessions linked to the target media app.
+     */
     @NonNull
     protected abstract List<RoutingSessionInfo> getRoutingSessionsForPackage();
 
@@ -206,9 +207,6 @@
     protected abstract RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId);
 
     @NonNull
-    protected abstract List<MediaRoute2Info> getAllRoutes();
-
-    @NonNull
     protected abstract List<MediaRoute2Info> getAvailableRoutesFromRouter();
 
     @NonNull
@@ -256,8 +254,8 @@
      * @return If add device successful return {@code true}, otherwise return {@code false}
      */
     boolean addDeviceToPlayMedia(MediaDevice device) {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null || !info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
+        final RoutingSessionInfo info = getActiveRoutingSession();
+        if (!info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
             Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
                     + device.getName());
             return false;
@@ -267,13 +265,11 @@
         return true;
     }
 
-    private RoutingSessionInfo getRoutingSessionInfo() {
-        final List<RoutingSessionInfo> sessionInfos = getRoutingSessionsForPackage();
-
-        if (sessionInfos.isEmpty()) {
-            return null;
-        }
-        return sessionInfos.get(sessionInfos.size() - 1);
+    @NonNull
+    private RoutingSessionInfo getActiveRoutingSession() {
+        // List is never empty.
+        final List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage();
+        return sessions.get(sessions.size() - 1);
     }
 
     boolean isRoutingSessionAvailableForVolumeControl() {
@@ -311,8 +307,8 @@
      * @return If device stop successful return {@code true}, otherwise return {@code false}
      */
     boolean removeDeviceFromPlayMedia(MediaDevice device) {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null || !info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
+        final RoutingSessionInfo info = getActiveRoutingSession();
+        if (!info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
             Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
                     + device.getName());
             return false;
@@ -326,13 +322,7 @@
      * Release session to stop playing media on MediaDevice.
      */
     boolean releaseSession() {
-        final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
-        if (sessionInfo == null) {
-            Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
-            return false;
-        }
-
-        releaseSession(sessionInfo);
+        releaseSession(getActiveRoutingSession());
         return true;
     }
 
@@ -342,12 +332,7 @@
      */
     @NonNull
     List<MediaDevice> getSelectableMediaDevices() {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null) {
-            Log.w(TAG, "getSelectableMediaDevices() cannot find selectable MediaDevice from : "
-                    + mPackageName);
-            return Collections.emptyList();
-        }
+        final RoutingSessionInfo info = getActiveRoutingSession();
 
         final List<MediaDevice> deviceList = new ArrayList<>();
         for (MediaRoute2Info route : getSelectableRoutes(info)) {
@@ -364,12 +349,7 @@
      */
     @NonNull
     List<MediaDevice> getDeselectableMediaDevices() {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null) {
-            Log.d(TAG, "getDeselectableMediaDevices() cannot find deselectable MediaDevice from : "
-                    + mPackageName);
-            return Collections.emptyList();
-        }
+        final RoutingSessionInfo info = getActiveRoutingSession();
 
         final List<MediaDevice> deviceList = new ArrayList<>();
         for (MediaRoute2Info route : getDeselectableRoutes(info)) {
@@ -387,13 +367,7 @@
      */
     @NonNull
     List<MediaDevice> getSelectedMediaDevices() {
-        RoutingSessionInfo info = getRoutingSessionInfo();
-
-        if (info == null) {
-            Log.w(TAG, "getSelectedMediaDevices() cannot find selectable MediaDevice from : "
-                    + mPackageName);
-            return Collections.emptyList();
-        }
+        RoutingSessionInfo info = getActiveRoutingSession();
 
         final List<MediaDevice> deviceList = new ArrayList<>();
         for (MediaRoute2Info route : getSelectedRoutes(info)) {
@@ -427,15 +401,8 @@
      * @param volume the value of volume
      */
     void adjustSessionVolume(int volume) {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null) {
-            Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
-                    + mPackageName);
-            return;
-        }
-
         Log.d(TAG, "adjustSessionVolume() adjust volume: " + volume + ", with : " + mPackageName);
-        setSessionVolume(info, volume);
+        setSessionVolume(getActiveRoutingSession(), volume);
     }
 
     /**
@@ -444,14 +411,7 @@
      * @return  maximum volume of the session, and return -1 if not found.
      */
     public int getSessionVolumeMax() {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null) {
-            Log.w(TAG, "getSessionVolumeMax() can't find corresponding RoutingSession with : "
-                    + mPackageName);
-            return -1;
-        }
-
-        return info.getVolumeMax();
+        return getActiveRoutingSession().getVolumeMax();
     }
 
     /**
@@ -460,24 +420,11 @@
      * @return current volume of the session, and return -1 if not found.
      */
     public int getSessionVolume() {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null) {
-            Log.w(TAG, "getSessionVolume() can't find corresponding RoutingSession with : "
-                    + mPackageName);
-            return -1;
-        }
-
-        return info.getVolume();
+        return getActiveRoutingSession().getVolume();
     }
 
     CharSequence getSessionName() {
-        final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info == null) {
-            Log.w(TAG, "Unable to get session name for package: " + mPackageName);
-            return null;
-        }
-
-        return info.getName();
+        return getActiveRoutingSession().getName();
     }
 
     @TargetApi(Build.VERSION_CODES.R)
@@ -503,26 +450,24 @@
         }
     }
     private synchronized List<MediaRoute2Info> getAvailableRoutes() {
-        List<MediaRoute2Info> infos = new ArrayList<>();
-        RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo();
-        List<MediaRoute2Info> selectedRouteInfos = new ArrayList<>();
-        if (routingSessionInfo != null) {
-            selectedRouteInfos = getSelectedRoutes(routingSessionInfo);
-            infos.addAll(selectedRouteInfos);
-            infos.addAll(getSelectableRoutes(routingSessionInfo));
-        }
-        final List<MediaRoute2Info> transferableRoutes =
-                getTransferableRoutes(mPackageName);
+        List<MediaRoute2Info> availableRoutes = new ArrayList<>();
+        RoutingSessionInfo activeSession = getActiveRoutingSession();
+
+        List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(activeSession);
+        availableRoutes.addAll(selectedRoutes);
+        availableRoutes.addAll(getSelectableRoutes(activeSession));
+
+        final List<MediaRoute2Info> transferableRoutes = getTransferableRoutes(mPackageName);
         for (MediaRoute2Info transferableRoute : transferableRoutes) {
             boolean alreadyAdded = false;
-            for (MediaRoute2Info mediaRoute2Info : infos) {
+            for (MediaRoute2Info mediaRoute2Info : availableRoutes) {
                 if (TextUtils.equals(transferableRoute.getId(), mediaRoute2Info.getId())) {
                     alreadyAdded = true;
                     break;
                 }
             }
             if (!alreadyAdded) {
-                infos.add(transferableRoute);
+                availableRoutes.add(transferableRoute);
             }
         }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
@@ -531,13 +476,13 @@
                 final List<RouteListingPreference.Item> preferenceRouteListing =
                         Api34Impl.composePreferenceRouteListing(
                                 routeListingPreference);
-                infos = Api34Impl.arrangeRouteListByPreference(selectedRouteInfos,
+                availableRoutes = Api34Impl.arrangeRouteListByPreference(selectedRoutes,
                         getAvailableRoutesFromRouter(),
                                 preferenceRouteListing);
             }
-            return Api34Impl.filterDuplicatedIds(infos);
+            return Api34Impl.filterDuplicatedIds(availableRoutes);
         } else {
-            return infos;
+            return availableRoutes;
         }
     }
 
@@ -610,7 +555,7 @@
         }
 
         if (mediaDevice != null
-                && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
+                && getActiveRoutingSession().getSelectedRoutes().contains(route.getId())) {
             mediaDevice.setState(STATE_SELECTED);
             if (mCurrentConnectedDevice == null) {
                 mCurrentConnectedDevice = mediaDevice;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index cf8906d..453e807 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -86,18 +86,6 @@
     }
 
     @Override
-    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
-        final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
-        if (info != null) {
-            // TODO: b/279555229 - provide real user handle and package name of a caller.
-            mRouterManager.transfer(
-                    info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
         mRouterManager.selectRoute(info, route);
     }
@@ -174,12 +162,6 @@
 
     @Override
     @NonNull
-    protected List<MediaRoute2Info> getAllRoutes() {
-        return mRouterManager.getAllRoutes();
-    }
-
-    @Override
-    @NonNull
     protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
         return mRouterManager.getAvailableRoutes(mPackageName);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index 5b1c8ef..ea4de39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -58,11 +58,6 @@
     }
 
     @Override
-    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
-        return false;
-    }
-
-    @Override
     protected void transferToRoute(@NonNull MediaRoute2Info route) {
         // Do nothing.
     }
@@ -136,12 +131,6 @@
 
     @NonNull
     @Override
-    protected List<MediaRoute2Info> getAllRoutes() {
-        return Collections.emptyList();
-    }
-
-    @NonNull
-    @Override
     protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
         return Collections.emptyList();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index c8c8b672..0f08605 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -114,17 +114,6 @@
     }
 
     @Override
-    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
-        if (device.mRouteInfo == null) {
-            return false;
-        }
-
-        RoutingController controller = mRouter.getSystemController();
-        mRouter.transfer(controller, device.mRouteInfo);
-        return true;
-    }
-
-    @Override
     protected void transferToRoute(@NonNull MediaRoute2Info route) {
         mRouter.transferTo(route);
     }
@@ -241,12 +230,6 @@
 
     @NonNull
     @Override
-    protected List<MediaRoute2Info> getAllRoutes() {
-        return mRouter.getAllRoutes();
-    }
-
-    @NonNull
-    @Override
     protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
         return mRouter.getRoutes();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index 701f008..7ad54e1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -17,6 +17,7 @@
 package com.android.settingslib;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doReturn;
@@ -28,6 +29,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyResourcesManager;
 import android.content.Context;
+import android.content.Intent;
 import android.view.View;
 import android.widget.TextView;
 
@@ -87,6 +89,19 @@
     }
 
     @Test
+    public void bindPreference_disabledByEcm_shouldDisplayDisabledSummary() {
+        final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
+        when(mViewHolder.itemView.findViewById(android.R.id.summary))
+                .thenReturn(summaryView);
+
+        mHelper.setDisabledByEcm(mock(Intent.class));
+        mHelper.onBindViewHolder(mViewHolder);
+
+        verify(mPreference).setSummary(R.string.disabled_by_app_ops_text);
+        verify(summaryView, never()).setVisibility(View.GONE);
+    }
+
+    @Test
     public void bindPreference_notDisabled_shouldNotHideSummary() {
         final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
         when(mViewHolder.itemView.findViewById(android.R.id.summary))
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 290e63ca..c159d5e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -30,6 +30,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -124,7 +125,11 @@
 
     @Test
     public void stopScan_startFirst_callsUnregister() {
+        RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
         mInfoMediaManager.mRouterManager = mRouterManager;
+        // Since test is running in Robolectric, return a fake session to avoid NPE.
+        when(mRouterManager.getRoutingSessions(anyString())).thenReturn(List.of(sessionInfo));
+
         mInfoMediaManager.startScan();
         mInfoMediaManager.stopScan();
 
@@ -500,18 +505,6 @@
     }
 
     @Test
-    public void connectDeviceWithoutPackageName_noSession_returnFalse() {
-        final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, info);
-
-        final List<RoutingSessionInfo> infos = new ArrayList<>();
-
-        mShadowRouter2Manager.setRemoteSessions(infos);
-
-        assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
-    }
-
-    @Test
     public void onRoutesRemoved_getAvailableRoutes_shouldAddMediaDevice() {
         final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
         final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
@@ -678,17 +671,6 @@
     }
 
     @Test
-    public void getSessionVolumeMax_routeSessionInfoIsNull_returnNotFound() {
-        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
-        final RoutingSessionInfo info = null;
-        routingSessionInfos.add(info);
-
-        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
-        assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
-    }
-
-    @Test
     public void getSessionVolume_containPackageName_returnMaxVolume() {
         final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
         final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -703,17 +685,6 @@
     }
 
     @Test
-    public void getSessionVolume_routeSessionInfoIsNull_returnNotFound() {
-        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
-        final RoutingSessionInfo info = null;
-        routingSessionInfos.add(info);
-
-        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
-        assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
-    }
-
-    @Test
     public void getRemoteSessions_returnsRemoteSessions() {
         final List<RoutingSessionInfo> infos = new ArrayList<>();
         infos.add(mock(RoutingSessionInfo.class));
@@ -735,17 +706,6 @@
     }
 
     @Test
-    public void getSessionName_routeSessionInfoIsNull_returnNull() {
-        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
-        final RoutingSessionInfo info = null;
-        routingSessionInfos.add(info);
-
-        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
-        assertThat(mInfoMediaManager.getSessionName()).isNull();
-    }
-
-    @Test
     public void getSessionName_containPackageName_returnName() {
         final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
         final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 4ed1965..71f9ba27 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -15,6 +15,16 @@
 }
 
 flag {
+   name: "udfps_view_performance"
+   namespace: "systemui"
+   description: "Decrease screen off blocking calls by waiting until the device is finished going to sleep before adding the udfps view."
+   bug: "225183106"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
     name: "notification_async_group_header_inflation"
     namespace: "systemui"
     description: "Inflates the notification group summary header views from the background thread."
@@ -489,3 +499,33 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "clipboard_noninteractive_on_lockscreen"
+    namespace: "systemui"
+    description: "Prevents the interactive clipboard UI from appearing when device is locked"
+    bug: "317048495"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "trim_resources_with_background_trim_at_lock"
+    namespace: "systemui"
+    description: "Trim fonts and other caches when the device locks to lower memory consumption."
+    bug: "322143614"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "dedicated_notif_inflation_thread"
+    namespace: "systemui"
+    description: "Create a separate background thread for inflating notifications"
+    bug: "308967184"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7b21d09..dd043db 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -18,11 +18,14 @@
 
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
@@ -87,10 +90,15 @@
 }
 
 @Composable
-private fun LockscreenScene(
+private fun SceneScope.LockscreenScene(
     lockscreenContent: Lazy<LockscreenContent>,
     modifier: Modifier = Modifier,
 ) {
+    animateSceneFloatAsState(
+        value = QuickSettings.SharedValues.SquishinessValues.LockscreenSceneStarting,
+        key = QuickSettings.SharedValues.TilesSquishiness,
+    )
+
     lockscreenContent
         .get()
         .Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
index c418490..7a73c58 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -73,7 +73,7 @@
         BurnInParameters(
             clockControllerProvider = { clock },
             topInset = topInset,
-            statusViewTop = topmostTop,
+            minViewY = topmostTop,
         )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 735c433..61b2d4e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -21,8 +21,8 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.media.controls.ui.MediaCarouselController
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.util.animation.MeasurementInput
 
 private object MediaCarousel {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index de8f2ec..5d0b9ba 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.ui.composable
 
-import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,14 +31,12 @@
 import com.android.compose.animation.scene.MovableElementScenePicker
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.ValueKey
 import com.android.compose.modifiers.thenIf
-import com.android.compose.theme.colorAttr
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
-import com.android.systemui.res.R
-import com.android.systemui.scene.ui.composable.Gone
-import com.android.systemui.scene.ui.composable.Lockscreen
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Unsquishing
 import com.android.systemui.scene.ui.composable.QuickSettings as QuickSettingsSceneKey
 import com.android.systemui.scene.ui.composable.Shade
 
@@ -51,15 +48,24 @@
         )
 
     object Elements {
-        // TODO RENAME
         val Content =
             ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
-        val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
         val FooterActions = ElementKey("QuickSettingsFooterActions")
     }
+
+    object SharedValues {
+        val TilesSquishiness = ValueKey("QuickSettingsTileSquishiness")
+        object SquishinessValues {
+            val Default = 1f
+            val LockscreenSceneStarting = 0f
+            val GoneSceneStarting = 0.3f
+        }
+    }
 }
 
-private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
+private fun SceneScope.stateForQuickSettingsContent(
+    squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default
+): QSSceneAdapter.State {
     return when (val transitionState = layoutState.transitionState) {
         is TransitionState.Idle -> {
             when (transitionState.currentScene) {
@@ -73,10 +79,10 @@
                 when {
                     fromScene == Shade && toScene == QuickSettingsSceneKey -> Expanding(progress)
                     fromScene == QuickSettingsSceneKey && toScene == Shade -> Collapsing(progress)
-                    toScene == Shade -> QSSceneAdapter.State.QQS
-                    toScene == QuickSettingsSceneKey -> QSSceneAdapter.State.QS
-                    toScene == Gone -> QSSceneAdapter.State.CLOSED
-                    toScene == Lockscreen -> QSSceneAdapter.State.CLOSED
+                    fromScene == Shade || toScene == Shade -> Unsquishing(squishiness)
+                    fromScene == QuickSettingsSceneKey || toScene == QuickSettingsSceneKey -> {
+                        QSSceneAdapter.State.QS
+                    }
                     else ->
                         error(
                             "Bad transition for QuickSettings: fromScene=$fromScene," +
@@ -90,14 +96,24 @@
 /**
  * This composable will show QuickSettingsContent in the correct state (as determined by its
  * [SceneScope]).
+ *
+ * If adding to scenes not in:
+ * * QuickSettingsScene
+ * * ShadeScene
+ *
+ * amend:
+ * * [stateForQuickSettingsContent],
+ * * [QuickSettings.SCENES],
+ * * this doc.
  */
 @Composable
 fun SceneScope.QuickSettings(
     qsSceneAdapter: QSSceneAdapter,
     heightProvider: () -> Int,
     modifier: Modifier = Modifier,
+    squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
 ) {
-    val contentState = stateForQuickSettingsContent()
+    val contentState = stateForQuickSettingsContent(squishiness)
 
     MovableElement(
         key = QuickSettings.Elements.Content,
@@ -136,7 +152,7 @@
                     modifier.fillMaxWidth().thenIf(isCustomizing) { Modifier.fillMaxHeight() }
             ) {
                 AndroidView(
-                    modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
+                    modifier = Modifier.fillMaxWidth(),
                     factory = { _ ->
                         qsSceneAdapter.setState(state)
                         view
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d36345a3..66cef86 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -54,6 +54,7 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.compose.modifiers.sysuiResTag
@@ -128,6 +129,7 @@
             remember(lifecycleOwner, viewModel) {
                 viewModel.getFooterActionsViewModel(lifecycleOwner)
             }
+        animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
 
         // ############## SCROLLING ################
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index f90f29d..9ca751e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -19,9 +19,12 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
@@ -63,6 +66,10 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
+        animateSceneFloatAsState(
+            value = QuickSettings.SharedValues.SquishinessValues.GoneSceneStarting,
+            key = QuickSettings.SharedValues.TilesSquishiness,
+        )
         Spacer(modifier.fillMaxSize())
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 6f115d8..30c82f4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -13,7 +13,9 @@
 ) {
     spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
 
-    fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
+    fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
+    fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
+    fractionRange(start = .58f) { fade(ShadeHeader.Elements.PrivacyChip) }
     translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
     translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index e71f996..48ab68a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.scene.ui.composable.transitions
 
 import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.lockscreenToShadeTransition(
@@ -13,15 +13,12 @@
 ) {
     spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
 
-    fractionRange(end = 0.5f) {
-        fade(Shade.Elements.BackgroundScrim)
-        translate(
-            QuickSettings.Elements.CollapsedGrid,
-            Edge.Top,
-            startsOutsideLayoutBounds = false,
-        )
+    fractionRange(end = 0.5f) { fade(Shade.Elements.BackgroundScrim) }
+    translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+    fractionRange(start = 0.5f) {
+        fade(QuickSettings.Elements.Content)
+        fade(Notifications.Elements.NotificationScrim)
     }
-    fractionRange(start = 0.5f) { fade(Notifications.Elements.NotificationScrim) }
 }
 
 private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index d5c2a03..3c15da1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -13,10 +13,15 @@
     translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
     timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
 
-    translate(ShadeHeader.Elements.CollapsedContent, y = ShadeHeader.Dimensions.CollapsedHeight)
+    translate(
+        ShadeHeader.Elements.CollapsedContentStart,
+        y = ShadeHeader.Dimensions.CollapsedHeight
+    )
+    translate(ShadeHeader.Elements.CollapsedContentEnd, y = ShadeHeader.Dimensions.CollapsedHeight)
     translate(ShadeHeader.Elements.ExpandedContent, y = (-ShadeHeader.Dimensions.ExpandedHeight))
 
-    fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContent) }
+    fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
+    fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
 
     fractionRange(start = .58f) { fade(ShadeHeader.Elements.ExpandedContent) }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index b11edf7..00b494b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -20,6 +20,7 @@
 import android.view.ContextThemeWrapper
 import android.view.ViewGroup
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
@@ -48,6 +49,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.ValueKey
 import com.android.compose.animation.scene.animateSceneFloatAsState
@@ -57,9 +59,11 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.composable.QuickSettings
 import com.android.systemui.scene.ui.composable.Shade as ShadeKey
+import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -72,7 +76,9 @@
 object ShadeHeader {
     object Elements {
         val ExpandedContent = ElementKey("ShadeHeaderExpandedContent")
-        val CollapsedContent = ElementKey("ShadeHeaderCollapsedContent")
+        val CollapsedContentStart = ElementKey("ShadeHeaderCollapsedContentStart")
+        val CollapsedContentEnd = ElementKey("ShadeHeaderCollapsedContentEnd")
+        val PrivacyChip = ElementKey("PrivacyChip", scenePicker = LowestZIndexScenePicker)
     }
 
     object Keys {
@@ -106,15 +112,16 @@
                 cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
             }
         }
+    val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
 
     // This layout assumes it is globally positioned at (0, 0) and is the
     // same size as the screen.
     Layout(
-        modifier = modifier.element(ShadeHeader.Elements.CollapsedContent),
+        modifier = modifier,
         contents =
             listOf(
                 {
-                    Row {
+                    Row(modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart)) {
                         AndroidView(
                             factory = { context ->
                                 Clock(
@@ -132,31 +139,44 @@
                     }
                 },
                 {
-                    Row(horizontalArrangement = Arrangement.End) {
-                        SystemIconContainer {
-                            when (LocalWindowSizeClass.current.widthSizeClass) {
-                                WindowWidthSizeClass.Medium,
-                                WindowWidthSizeClass.Expanded ->
-                                    ShadeCarrierGroup(
-                                        viewModel = viewModel,
-                                        modifier = Modifier.align(Alignment.CenterVertically),
-                                    )
-                            }
-                            StatusIcons(
+                    if (isPrivacyChipVisible) {
+                        Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
+                            PrivacyChip(
                                 viewModel = viewModel,
-                                createTintedIconManager = createTintedIconManager,
-                                statusBarIconController = statusBarIconController,
-                                useExpandedFormat = useExpandedFormat,
-                                modifier =
-                                    Modifier.align(Alignment.CenterVertically)
-                                        .padding(end = 6.dp)
-                                        .weight(1f, fill = false)
+                                modifier = Modifier.align(Alignment.CenterEnd),
                             )
-                            BatteryIcon(
-                                createBatteryMeterViewController = createBatteryMeterViewController,
-                                useExpandedFormat = useExpandedFormat,
-                                modifier = Modifier.align(Alignment.CenterVertically),
-                            )
+                        }
+                    } else {
+                        Row(
+                            horizontalArrangement = Arrangement.End,
+                            modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
+                        ) {
+                            SystemIconContainer {
+                                when (LocalWindowSizeClass.current.widthSizeClass) {
+                                    WindowWidthSizeClass.Medium,
+                                    WindowWidthSizeClass.Expanded ->
+                                        ShadeCarrierGroup(
+                                            viewModel = viewModel,
+                                            modifier = Modifier.align(Alignment.CenterVertically),
+                                        )
+                                }
+                                StatusIcons(
+                                    viewModel = viewModel,
+                                    createTintedIconManager = createTintedIconManager,
+                                    statusBarIconController = statusBarIconController,
+                                    useExpandedFormat = useExpandedFormat,
+                                    modifier =
+                                        Modifier.align(Alignment.CenterVertically)
+                                            .padding(end = 6.dp)
+                                            .weight(1f, fill = false)
+                                )
+                                BatteryIcon(
+                                    createBatteryMeterViewController =
+                                        createBatteryMeterViewController,
+                                    useExpandedFormat = useExpandedFormat,
+                                    modifier = Modifier.align(Alignment.CenterVertically),
+                                )
+                            }
                         }
                     }
                 },
@@ -223,67 +243,77 @@
             .unsafeCompositionState(initialValue = 1f)
     val useExpandedFormat by
         remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
+    val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
 
-    Column(
-        verticalArrangement = Arrangement.Bottom,
-        modifier =
-            modifier
-                .element(ShadeHeader.Elements.ExpandedContent)
-                .fillMaxWidth()
-                .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
-    ) {
-        Row {
-            AndroidView(
-                factory = { context ->
-                    Clock(ContextThemeWrapper(context, R.style.TextAppearance_QS_Status), null)
-                },
-                modifier =
-                    Modifier.align(Alignment.CenterVertically)
-                        // use graphicsLayer instead of Modifier.scale to anchor transform to
-                        // the (start, top) corner
-                        .graphicsLayer(
-                            scaleX = 2.57f,
-                            scaleY = 2.57f,
-                            transformOrigin =
-                                TransformOrigin(
-                                    when (LocalLayoutDirection.current) {
-                                        LayoutDirection.Ltr -> 0f
-                                        LayoutDirection.Rtl -> 1f
-                                    },
-                                    0.5f
-                                )
-                        ),
-            )
-            Spacer(modifier = Modifier.weight(1f))
-            ShadeCarrierGroup(
-                viewModel = viewModel,
-                modifier = Modifier.align(Alignment.CenterVertically),
-            )
-        }
-        Spacer(modifier = Modifier.width(5.dp))
-        Row {
-            VariableDayDate(
-                viewModel = viewModel,
-                modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically),
-            )
-            Spacer(modifier = Modifier.weight(1f))
-            SystemIconContainer {
-                StatusIcons(
+    Box(modifier = modifier) {
+        if (isPrivacyChipVisible) {
+            Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
+                PrivacyChip(
                     viewModel = viewModel,
-                    createTintedIconManager = createTintedIconManager,
-                    statusBarIconController = statusBarIconController,
-                    useExpandedFormat = useExpandedFormat,
+                    modifier = Modifier.align(Alignment.CenterEnd),
+                )
+            }
+        }
+        Column(
+            verticalArrangement = Arrangement.Bottom,
+            modifier =
+                Modifier.element(ShadeHeader.Elements.ExpandedContent)
+                    .fillMaxWidth()
+                    .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
+        ) {
+            Row {
+                AndroidView(
+                    factory = { context ->
+                        Clock(ContextThemeWrapper(context, R.style.TextAppearance_QS_Status), null)
+                    },
                     modifier =
                         Modifier.align(Alignment.CenterVertically)
-                            .padding(end = 6.dp)
-                            .weight(1f, fill = false),
+                            // use graphicsLayer instead of Modifier.scale to anchor transform to
+                            // the (start, top) corner
+                            .graphicsLayer(
+                                scaleX = 2.57f,
+                                scaleY = 2.57f,
+                                transformOrigin =
+                                    TransformOrigin(
+                                        when (LocalLayoutDirection.current) {
+                                            LayoutDirection.Ltr -> 0f
+                                            LayoutDirection.Rtl -> 1f
+                                        },
+                                        0.5f
+                                    )
+                            ),
                 )
-                BatteryIcon(
-                    useExpandedFormat = useExpandedFormat,
-                    createBatteryMeterViewController = createBatteryMeterViewController,
+                Spacer(modifier = Modifier.weight(1f))
+                ShadeCarrierGroup(
+                    viewModel = viewModel,
                     modifier = Modifier.align(Alignment.CenterVertically),
                 )
             }
+            Spacer(modifier = Modifier.width(5.dp))
+            Row {
+                VariableDayDate(
+                    viewModel = viewModel,
+                    modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically),
+                )
+                Spacer(modifier = Modifier.weight(1f))
+                SystemIconContainer {
+                    StatusIcons(
+                        viewModel = viewModel,
+                        createTintedIconManager = createTintedIconManager,
+                        statusBarIconController = statusBarIconController,
+                        useExpandedFormat = useExpandedFormat,
+                        modifier =
+                            Modifier.align(Alignment.CenterVertically)
+                                .padding(end = 6.dp)
+                                .weight(1f, fill = false),
+                    )
+                    BatteryIcon(
+                        useExpandedFormat = useExpandedFormat,
+                        createBatteryMeterViewController = createBatteryMeterViewController,
+                        modifier = Modifier.align(Alignment.CenterVertically),
+                    )
+                }
+            }
         }
     }
 }
@@ -359,7 +389,14 @@
 ) {
     val carrierIconSlots =
         listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile))
+    val cameraSlot = stringResource(id = com.android.internal.R.string.status_bar_camera)
+    val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone)
+    val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location)
+
     val isSingleCarrier by viewModel.isSingleCarrier.collectAsState()
+    val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsState()
+    val isMicCameraIndicationEnabled by viewModel.isMicCameraIndicationEnabled.collectAsState()
+    val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsState()
 
     AndroidView(
         factory = { context ->
@@ -382,6 +419,25 @@
             } else {
                 iconContainer.addIgnoredSlots(carrierIconSlots)
             }
+
+            if (isPrivacyChipEnabled) {
+                if (isMicCameraIndicationEnabled) {
+                    iconContainer.addIgnoredSlot(cameraSlot)
+                    iconContainer.addIgnoredSlot(micSlot)
+                } else {
+                    iconContainer.removeIgnoredSlot(cameraSlot)
+                    iconContainer.removeIgnoredSlot(micSlot)
+                }
+                if (isLocationIndicationEnabled) {
+                    iconContainer.addIgnoredSlot(locationSlot)
+                } else {
+                    iconContainer.removeIgnoredSlot(locationSlot)
+                }
+            } else {
+                iconContainer.removeIgnoredSlot(cameraSlot)
+                iconContainer.removeIgnoredSlot(micSlot)
+                iconContainer.removeIgnoredSlot(locationSlot)
+            }
         },
         modifier = modifier,
     )
@@ -394,7 +450,28 @@
 ) {
     // TODO(b/298524053): add hover state for this container
     Row(
-        modifier = modifier.height(ShadeHeader.Dimensions.CollapsedHeight),
+        modifier = modifier.height(CollapsedHeight),
         content = content,
     )
 }
+
+@Composable
+private fun SceneScope.PrivacyChip(
+    viewModel: ShadeHeaderViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val privacyList by viewModel.privacyItems.collectAsState()
+
+    AndroidView(
+        factory = { context ->
+            val view =
+                OngoingPrivacyChip(context, null).also { privacyChip ->
+                    privacyChip.privacyList = privacyList
+                    privacyChip.setOnClickListener { viewModel.onPrivacyChipClicked(privacyChip) }
+                }
+            view
+        },
+        update = { it.privacyList = privacyList },
+        modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 25df3e4..ff6e895 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -40,14 +41,15 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.controls.ui.MediaCarouselController
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
 import com.android.systemui.qs.ui.composable.QuickSettings
@@ -73,7 +75,6 @@
 
 object Shade {
     object Elements {
-        val QuickSettings = ElementKey("ShadeQuickSettings")
         val MediaCarousel = ElementKey("ShadeMediaCarousel")
         val BackgroundScrim =
             ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
@@ -160,6 +161,8 @@
     val density = LocalDensity.current
     val layoutWidth = remember { mutableStateOf(0) }
     val maxNotifScrimTop = remember { mutableStateOf(0f) }
+    val tileSquishiness by
+        animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
 
     Box(
         modifier =
@@ -190,7 +193,11 @@
                             )
                             QuickSettings(
                                 viewModel.qsSceneAdapter,
-                                { viewModel.qsSceneAdapter.qqsHeight },
+                                {
+                                    (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
+                                        .roundToInt()
+                                },
+                                squishiness = tileSquishiness,
                             )
 
                             if (viewModel.isMediaVisible()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
index 0cf4367..d401261 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -47,7 +47,7 @@
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         Row(
-            modifier = modifier.height(48.dp).fillMaxWidth(),
+            modifier = modifier.height(if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
             horizontalArrangement = Arrangement.SpaceBetween,
             verticalAlignment = Alignment.CenterVertically,
         ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index 86eb849..2285128 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -52,7 +52,7 @@
         if (layout.footerComponents.isNotEmpty()) {
             Row(
                 modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-                horizontalArrangement = Arrangement.spacedBy(20.dp),
+                horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
             ) {
                 for (component in layout.footerComponents) {
                     AnimatedVisibility(component.isVisible) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
index 10731c7..8df8d2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -27,8 +27,8 @@
     val orientation: Int
         get() = state.orientation
 
-    /** Is true when Volume Panel is using wide-screen layout and false the otherwise. */
-    val isWideScreen: Boolean
+    /** Is true when Volume Panel is using large-screen layout and false the otherwise. */
+    val isLargeScreen: Boolean
         get() = state.isWideScreen
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index dd63420..8a1e6a8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -19,6 +19,7 @@
 import android.content.res.Configuration
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -26,6 +27,7 @@
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.navigationBarsPadding
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -43,6 +45,8 @@
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 
+private val padding = 24.dp
+
 @Composable
 fun VolumePanelRoot(
     viewModel: VolumePanelViewModel,
@@ -84,7 +88,18 @@
                     shape = RoundedCornerShape(topStart = radius, topEnd = radius),
                     color = MaterialTheme.colorScheme.surfaceContainer,
                 ) {
-                    Column { components?.let { componentsState -> Components(componentsState) } }
+                    components?.let { componentsState ->
+                        Components(
+                            componentsState,
+                            Modifier.padding(
+                                    start = padding,
+                                    top = padding,
+                                    end = padding,
+                                    bottom = 20.dp,
+                                )
+                                .navigationBarsPadding()
+                        )
+                    }
                 }
             }
         }
@@ -92,36 +107,35 @@
 }
 
 @Composable
-private fun VolumePanelComposeScope.Components(components: ComponentsLayout) {
-    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-        VerticalVolumePanelContent(
-            components,
-            modifier = Modifier.padding(24.dp),
-        )
-    } else {
-        HorizontalVolumePanelContent(
-            components,
-            modifier =
-                Modifier.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 20.dp)
-                    .heightIn(max = 236.dp),
-        )
+private fun VolumePanelComposeScope.Components(
+    layout: ComponentsLayout,
+    modifier: Modifier = Modifier
+) {
+    var columnModifier = modifier.widthIn(max = 800.dp)
+    if (!isLargeScreen && orientation != Configuration.ORIENTATION_PORTRAIT) {
+        columnModifier = columnModifier.heightIn(max = 332.dp)
     }
+    Column(modifier = columnModifier, verticalArrangement = Arrangement.spacedBy(padding)) {
+        if (orientation == Configuration.ORIENTATION_PORTRAIT || isLargeScreen) {
+            VerticalVolumePanelContent(layout)
+        } else {
+            HorizontalVolumePanelContent(layout)
+        }
+        BottomBar(layout = layout, modifier = Modifier)
+    }
+}
 
-    if (components.bottomBarComponent.isVisible) {
-        val horizontalPadding =
-            dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding)
+@Composable
+private fun VolumePanelComposeScope.BottomBar(
+    layout: ComponentsLayout,
+    modifier: Modifier = Modifier
+) {
+    if (layout.bottomBarComponent.isVisible) {
         Box(
-            modifier =
-                Modifier.fillMaxWidth()
-                    .navigationBarsPadding()
-                    .padding(
-                        start = horizontalPadding,
-                        end = horizontalPadding,
-                        bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding),
-                    ),
+            modifier = modifier.fillMaxWidth(),
             contentAlignment = Alignment.Center,
         ) {
-            with(components.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
+            with(layout.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
                 Content(Modifier)
             }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 45f98be..1cdc2b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index 824733b..5a7cbf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -67,7 +67,7 @@
 
     @Test
     fun isCommunalEnabled_false() =
-        testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+        testScope.runTest { assertThat(underTest.isCommunalEnabled.value).isFalse() }
 
     @Test
     fun isCommunalAvailable_whenStorageUnlock_false() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 3ac19e4..53af4a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -123,7 +123,7 @@
         testScope.runTest {
             userRepository.setSelectedUserInfo(mainUser)
             runCurrent()
-            assertThat(underTest.isCommunalEnabled).isTrue()
+            assertThat(underTest.isCommunalEnabled.value).isTrue()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ddb8582..352bacc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -38,7 +38,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
 import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index b299ca7..cc322d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -43,8 +43,8 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 74fa465..b0f59fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -127,7 +127,7 @@
     @Test
     fun translationAndScale_whenFullyDozing() =
         testScope.runTest {
-            burnInParameters = burnInParameters.copy(statusViewTop = 100)
+            burnInParameters = burnInParameters.copy(minViewY = 100)
             val translationX by collectLastValue(underTest.translationX(burnInParameters))
             val translationY by collectLastValue(underTest.translationY(burnInParameters))
             val scale by collectLastValue(underTest.scale(burnInParameters))
@@ -182,11 +182,77 @@
         }
 
     @Test
-    fun translationAndScale_whenFullyDozing_staysOutOfTopInset() =
+    fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
             burnInParameters =
                 burnInParameters.copy(
-                    statusViewTop = 100,
+                    minViewY = 100,
+                    topInset = 80,
+                )
+            val translationX by collectLastValue(underTest.translationX(burnInParameters))
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            val scale by collectLastValue(underTest.scale(burnInParameters))
+
+            // Set to dozing (on AOD)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                ),
+                validateStep = false,
+            )
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = -30,
+                    scale = 0.5f,
+                )
+            assertThat(translationX).isEqualTo(20)
+            // -20 instead of -30, due to inset of 80
+            assertThat(translationY).isEqualTo(-20)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 0.5f,
+                        scaleClockOnly = true,
+                    )
+                )
+
+            // Set to the beginning of GONE->AOD transition
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED
+                ),
+                validateStep = false,
+            )
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 1f,
+                        scaleClockOnly = true,
+                    )
+                )
+        }
+
+    @Test
+    fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+            burnInParameters =
+                burnInParameters.copy(
+                    minViewY = 100,
                     topInset = 80,
                 )
             val translationX by collectLastValue(underTest.translationX(burnInParameters))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index c381749..31b67b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,6 +43,7 @@
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val repository = kosmos.fakeKeyguardTransitionRepository
+    val shadeRepository = kosmos.fakeShadeRepository
     val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     val underTest = kosmos.aodToLockscreenTransitionViewModel
 
@@ -59,6 +61,38 @@
         }
 
     @Test
+    fun notificationAlpha_whenShadeIsExpanded_equalsOne() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.notificationAlpha)
+
+            shadeRepository.setQsExpansion(0.5f)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(alpha).isEqualTo(1f)
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isEqualTo(1f)
+            repository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun notificationAlpha_whenShadeIsNotExpanded_usesTransitionValue() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.notificationAlpha)
+
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(alpha).isEqualTo(0f)
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isEqualTo(0.5f)
+            repository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
     fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
         testScope.runTest {
             val viewState = ViewStateAccessor(alpha = { 0.5f })
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index e04cbfd..503fd34 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
 import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -72,6 +73,7 @@
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
     private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
     private val dozeParameters = kosmos.dozeParameters
+    private val shadeRepository = kosmos.fakeShadeRepository
     private val underTest by lazy { kosmos.keyguardRootViewModel }
 
     private val viewState = ViewStateAccessor()
@@ -308,4 +310,22 @@
 
             assertThat(alpha).isEqualTo(1.0f)
         }
+
+    @Test
+    fun alpha_emitsOnShadeExpansion() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            shadeRepository.setQsExpansion(0f)
+            assertThat(alpha).isEqualTo(1f)
+
+            shadeRepository.setQsExpansion(0.5f)
+            assertThat(alpha).isEqualTo(0f)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt
new file mode 100644
index 0000000..62c9163
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MinimumTilesResourceRepositoryTest : SysuiTestCase() {
+
+    val testableResources = context.orCreateTestableResources
+
+    @Test
+    fun minimumQSTiles_followsConfig() {
+        val minTwo = 2
+        testableResources.addOverride(R.integer.quick_settings_min_num_tiles, minTwo)
+        val underTest = MinimumTilesResourceRepository(context.resources)
+        assertThat(underTest.minNumberOfTiles).isEqualTo(minTwo)
+
+        val minSix = 6
+        testableResources.addOverride(R.integer.quick_settings_min_num_tiles, minSix)
+        val otherUnderTest = MinimumTilesResourceRepository(context.resources)
+        assertThat(otherUnderTest.minNumberOfTiles).isEqualTo(minSix)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 3418977..37d4721 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -20,11 +20,11 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -187,6 +187,22 @@
             assertThat(loadTilesForUser(0)).isEqualTo(DEFAULT_TILES)
         }
 
+    @Test
+    fun prependDefault() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+            val startingTiles = listOf(TileSpec.create("e"), TileSpec.create("f"))
+
+            underTest.setTiles(0, startingTiles)
+            runCurrent()
+
+            underTest.prependDefault(0)
+
+            assertThat(tiles!!)
+                .containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles)
+        }
+
     private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
         secureSettings.putStringForUser(SETTING, specs, forUser)
         runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 1e2784a..634c5fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.domain.model.TileModel
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
@@ -82,6 +83,7 @@
         FakeCustomTileAddedRepository()
     private val pipelineFlags = QSPipelineFlagsRepository()
     private val tileLifecycleManagerFactory = TLMFactory()
+    private val minimumTilesRepository = MinimumTilesFixedRepository()
 
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
 
@@ -114,6 +116,7 @@
                 tileSpecRepository = tileSpecRepository,
                 installedTilesComponentRepository = installedTilesPackageRepository,
                 userRepository = userRepository,
+                minimumTilesRepository = minimumTilesRepository,
                 customTileStatePersister = customTileStatePersister,
                 tileFactory = tileFactory,
                 newQSTileFactory = { newQSTileFactory },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
new file mode 100644
index 0000000..90c8304
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This integration test is for testing the solution to b/324575996. In particular, when restoring
+ * from a device that uses different specs for tiles, we may end up with empty (or mostly empty) QS.
+ * In that case, we want to prepend the default tiles instead.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NoLowNumberOfTilesTest : SysuiTestCase() {
+
+    private val USER_0_INFO =
+        UserInfo(
+            0,
+            "zero",
+            "",
+            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+        )
+
+    private val defaultTiles =
+        listOf(
+            TileSpec.create("internet"),
+            TileSpec.create("bt"),
+        )
+
+    private val kosmos =
+        Kosmos().apply {
+            fakeMinimumTilesRepository = MinimumTilesFixedRepository(minNumberOfTiles = 2)
+            fakeUserTracker.set(listOf(USER_0_INFO), 0)
+            qsTileFactory = FakeQSFactory(::tileCreator)
+            fakeDefaultTilesRepository = FakeDefaultTilesRepository(defaultTiles)
+        }
+
+    private val currentUser: Int
+        get() = kosmos.userTracker.userId
+
+    private val goodTile = TileSpec.create("correct")
+
+    private val restoredTiles =
+        listOf(
+            TileSpec.create("OEM:internet"),
+            TileSpec.create("OEM:bt"),
+            TileSpec.create("OEM:dnd"),
+            // This is not an installed component so a tile won't be created
+            TileSpec.create(ComponentName.unflattenFromString("oem/.tile")!!),
+            TileSpec.create("OEM:flashlight"),
+            goodTile,
+        )
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+
+        with(kosmos) {
+            restoreReconciliationInteractor.start()
+            autoAddInteractor.init(kosmos.currentTilesInteractor)
+        }
+    }
+
+    @Test
+    fun noLessThanTwoTilesAfterOEMRestore_prependedDefault() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+                runCurrent()
+
+                assertThat(tiles!!).isNotEmpty()
+
+                val restoreData = RestoreData(restoredTiles, emptySet(), currentUser)
+                fakeRestoreRepository.onDataRestored(restoreData)
+                runCurrent()
+
+                assertThat(tiles!!.map { it.spec }).isEqualTo(defaultTiles + listOf(goodTile))
+            }
+        }
+
+    @Test
+    fun noEmptyTilesAfterSettingTilesToUnknownNames() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+                runCurrent()
+
+                assertThat(tiles!!).isNotEmpty()
+
+                val badTiles = listOf(TileSpec.create("OEM:unknown_tile"))
+                currentTilesInteractor.setTiles(badTiles)
+                runCurrent()
+
+                assertThat(tiles!!.map { it.spec }).isEqualTo(defaultTiles)
+            }
+        }
+
+    private fun tileCreator(spec: String): QSTile? {
+        return if (spec.contains("OEM")) {
+            null // We don't know how to create OEM spec tiles
+        } else {
+            FakeQSTile(currentUser)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index f8573cc2..3c0ab24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -259,6 +259,37 @@
         }
 
     @Test
+    fun state_unsquishing() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+            val squishiness = 0.342f
+
+            underTest.inflate(context)
+            runCurrent()
+            clearInvocations(qsImpl!!)
+
+            underTest.setState(QSSceneAdapter.State.Unsquishing(squishiness))
+            with(qsImpl!!) {
+                verify(this).setQsVisible(true)
+                verify(this)
+                    .setQsExpansion(
+                        /* expansion= */ 0f,
+                        /* panelExpansionFraction= */ 1f,
+                        /* proposedTranslation= */ 0f,
+                        /* squishinessFraction= */ squishiness,
+                    )
+                verify(this).setListening(true)
+                verify(this).setExpanded(true)
+                verify(this)
+                    .setTransitionToFullShadeProgress(
+                        /* isTransitioningToFullShade= */ false,
+                        /* qsTransitionFraction= */ 1f,
+                        /* qsSquishinessFraction = */ squishiness,
+                    )
+            }
+        }
+
+    @Test
     fun customizing_QS() =
         testScope.runTest {
             val customizing by collectLastValue(underTest.isCustomizing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index d1bc686..e281383 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -29,6 +29,12 @@
 @EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class QSSceneAdapterTest : SysuiTestCase() {
+
+    @Test
+    fun expanding_squishiness1() {
+        assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness).isEqualTo(1f)
+    }
+
     @Test
     fun expandingSpecialValues() {
         assertThat(QSSceneAdapter.State.QQS).isEqualTo(QSSceneAdapter.State.Expanding(0f))
@@ -41,4 +47,11 @@
         assertThat(Collapsing(collapsingProgress))
             .isEqualTo(QSSceneAdapter.State.Expanding(1 - collapsingProgress))
     }
+
+    @Test
+    fun unsquishing_expansionSameAsQQS() {
+        val squishiness = 0.6f
+        assertThat(QSSceneAdapter.State.Unsquishing(squishiness).expansion)
+            .isEqualTo(QSSceneAdapter.State.QQS.expansion)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d47da3e..82862e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -26,11 +26,11 @@
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -58,7 +58,6 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
     private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
@@ -95,9 +94,9 @@
             ShadeHeaderViewModel(
                 applicationScope = testScope.backgroundScope,
                 context = context,
-                sceneInteractor = sceneInteractor,
                 mobileIconsInteractor = mobileIconsInteractor,
                 mobileIconsViewModel = mobileIconsViewModel,
+                privacyChipInteractor = kosmos.privacyChipInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 9f89d34..53b1d0e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -50,7 +50,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.model.SysUiState
 import com.android.systemui.model.sceneContainerPlugin
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -65,6 +65,7 @@
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
@@ -229,9 +230,9 @@
             ShadeHeaderViewModel(
                 applicationScope = testScope.backgroundScope,
                 context = context,
-                sceneInteractor = sceneInteractor,
                 mobileIconsInteractor = mobileIconsInteractor,
                 mobileIconsViewModel = mobileIconsViewModel,
+                privacyChipInteractor = kosmos.privacyChipInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt
new file mode 100644
index 0000000..613f256
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.content.Intent
+import android.safetycenter.SafetyCenterManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.privacy.PrivacyApplication
+import com.android.systemui.privacy.PrivacyConfig
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.privacy.PrivacyType
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyChipRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val broadcastDispatcher = kosmos.broadcastDispatcher
+
+    @Mock private lateinit var privacyConfig: PrivacyConfig
+    @Mock private lateinit var privacyItemController: PrivacyItemController
+    @Mock private lateinit var safetyCenterManager: SafetyCenterManager
+
+    lateinit var underTest: PrivacyChipRepositoryImpl
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        setUpUnderTest()
+    }
+
+    @Test
+    fun isSafetyCenterEnabled_startEnabled() =
+        testScope.runTest {
+            setUpUnderTest(true)
+
+            val actual by collectLastValue(underTest.isSafetyCenterEnabled)
+            runCurrent()
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isSafetyCenterEnabled_startDisabled() =
+        testScope.runTest {
+            setUpUnderTest(false)
+
+            val actual by collectLastValue(underTest.isSafetyCenterEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isSafetyCenterEnabled_updates() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isSafetyCenterEnabled)
+            runCurrent()
+
+            assertThat(actual).isFalse()
+
+            whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
+
+            broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED),
+            )
+
+            runCurrent()
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun privacyItems_updates() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.privacyItems)
+            runCurrent()
+
+            val callback =
+                withArgCaptor<PrivacyItemController.Callback> {
+                    verify(privacyItemController).addCallback(capture())
+                }
+
+            callback.onPrivacyItemsChanged(emptyList())
+            assertThat(actual).isEmpty()
+
+            val privacyItems =
+                listOf(
+                    PrivacyItem(
+                        privacyType = PrivacyType.TYPE_CAMERA,
+                        application = PrivacyApplication("", 0)
+                    ),
+                )
+            callback.onPrivacyItemsChanged(privacyItems)
+            assertThat(actual).isEqualTo(privacyItems)
+        }
+
+    @Test
+    fun isMicCameraIndicationEnabled_updates() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isMicCameraIndicationEnabled)
+            runCurrent()
+
+            val captor = kotlinArgumentCaptor<PrivacyConfig.Callback>()
+            verify(privacyConfig, times(2)).addCallback(captor.capture())
+            val callback = captor.allValues[0]
+
+            callback.onFlagMicCameraChanged(false)
+            assertThat(actual).isFalse()
+
+            callback.onFlagMicCameraChanged(true)
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isLocationIndicationEnabled_updates() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isLocationIndicationEnabled)
+            runCurrent()
+
+            val captor = kotlinArgumentCaptor<PrivacyConfig.Callback>()
+            verify(privacyConfig, times(2)).addCallback(captor.capture())
+            val callback = captor.allValues[1]
+
+            callback.onFlagLocationChanged(false)
+            assertThat(actual).isFalse()
+
+            callback.onFlagLocationChanged(true)
+            assertThat(actual).isTrue()
+        }
+
+    private fun setUpUnderTest(isSafetyCenterEnabled: Boolean = false) {
+        whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(isSafetyCenterEnabled)
+
+        underTest =
+            PrivacyChipRepositoryImpl(
+                applicationScope = kosmos.applicationCoroutineScope,
+                privacyConfig = privacyConfig,
+                privacyItemController = privacyItemController,
+                backgroundDispatcher = kosmos.testDispatcher,
+                broadcastDispatcher = broadcastDispatcher,
+                safetyCenterManager = safetyCenterManager,
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt
new file mode 100644
index 0000000..f0293a8e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyApplication
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyType
+import com.android.systemui.privacy.privacyDialogController
+import com.android.systemui.privacy.privacyDialogControllerV2
+import com.android.systemui.shade.data.repository.fakePrivacyChipRepository
+import com.android.systemui.shade.data.repository.privacyChipRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyChipInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val privacyChipRepository = kosmos.fakePrivacyChipRepository
+    private val privacyDialogController = kosmos.privacyDialogController
+    private val privacyDialogControllerV2 = kosmos.privacyDialogControllerV2
+    @Mock private lateinit var privacyChip: OngoingPrivacyChip
+
+    val underTest = kosmos.privacyChipInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        whenever(privacyChip.context).thenReturn(this.context)
+    }
+
+    @Test
+    fun isChipVisible_updates() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isChipVisible)
+
+            privacyChipRepository.setPrivacyItems(emptyList())
+            runCurrent()
+
+            assertThat(actual).isFalse()
+
+            val privacyItems =
+                listOf(
+                    PrivacyItem(
+                        privacyType = PrivacyType.TYPE_CAMERA,
+                        application = PrivacyApplication("", 0)
+                    ),
+                )
+            privacyChipRepository.setPrivacyItems(privacyItems)
+            runCurrent()
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isChipEnabled_noIndicationEnabled() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isChipEnabled)
+
+            privacyChipRepository.setIsMicCameraIndicationEnabled(false)
+            privacyChipRepository.setIsLocationIndicationEnabled(false)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isChipEnabled_micCameraIndicationEnabled() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isChipEnabled)
+
+            privacyChipRepository.setIsMicCameraIndicationEnabled(true)
+            privacyChipRepository.setIsLocationIndicationEnabled(false)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isChipEnabled_locationIndicationEnabled() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isChipEnabled)
+
+            privacyChipRepository.setIsMicCameraIndicationEnabled(false)
+            privacyChipRepository.setIsLocationIndicationEnabled(true)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isChipEnabled_allIndicationEnabled() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isChipEnabled)
+
+            privacyChipRepository.setIsMicCameraIndicationEnabled(true)
+            privacyChipRepository.setIsLocationIndicationEnabled(true)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun onPrivacyChipClicked_safetyCenterEnabled() =
+        testScope.runTest {
+            privacyChipRepository.setIsSafetyCenterEnabled(true)
+
+            underTest.onPrivacyChipClicked(privacyChip)
+
+            verify(privacyDialogControllerV2).showDialog(any(), any())
+            verify(privacyDialogController, never()).showDialog(any())
+        }
+
+    @Test
+    fun onPrivacyChipClicked_safetyCenterDisabled() =
+        testScope.runTest {
+            privacyChipRepository.setIsSafetyCenterEnabled(false)
+
+            underTest.onPrivacyChipClicked(privacyChip)
+
+            verify(privacyDialogController).showDialog(any())
+            verify(privacyDialogControllerV2, never()).showDialog(any(), any())
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index c0aaab3..1ef3076 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -8,7 +8,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -31,7 +31,6 @@
 class ShadeHeaderViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -62,9 +61,9 @@
             ShadeHeaderViewModel(
                 applicationScope = testScope.backgroundScope,
                 context = context,
-                sceneInteractor = sceneInteractor,
                 mobileIconsInteractor = mobileIconsInteractor,
                 mobileIconsViewModel = mobileIconsViewModel,
+                privacyChipInteractor = kosmos.privacyChipInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 799e8f0..33c2d4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -27,10 +27,11 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -96,9 +97,9 @@
             ShadeHeaderViewModel(
                 applicationScope = testScope.backgroundScope,
                 context = context,
-                sceneInteractor = sceneInteractor,
                 mobileIconsInteractor = mobileIconsInteractor,
                 mobileIconsViewModel = mobileIconsViewModel,
+                privacyChipInteractor = kosmos.privacyChipInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 3e0082c..0641c61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -681,7 +681,7 @@
     @Test
     fun shadeCollapseFadeIn() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
+            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
             underTest.setShadeCollapseFadeInComplete(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index aa6b4ac..c804fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -1138,7 +1138,7 @@
 
     private fun setCameraProtectionBounds(protectionBounds: Rect) {
         val protectionInfo =
-            mock<CameraProtectionInfo> { whenever(this.cutoutBounds).thenReturn(protectionBounds) }
+            mock<CameraProtectionInfo> { whenever(this.bounds).thenReturn(protectionBounds) }
         whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
new file mode 100644
index 0000000..e31cdcd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.localMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.sliceViewManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncSliceRepositoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AncSliceRepository
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            val slice = FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+            whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(slice)
+
+            underTest =
+                AncSliceRepositoryImpl(
+                    localMediaRepositoryFactory,
+                    testScope.testScheduler,
+                    sliceViewManager,
+                )
+        }
+    }
+
+    @Test
+    fun noConnectedDevice_noSlice() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(null)
+
+                val slice by collectLastValue(underTest.ancSlice(1))
+                runCurrent()
+
+                assertThat(slice).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun connectedDevice_sliceReturned() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(createMediaDevice())
+
+                val slice by collectLastValue(underTest.ancSlice(1))
+                runCurrent()
+
+                assertThat(slice).isNotNull()
+            }
+        }
+    }
+
+    private fun createMediaDevice(sliceUri: String = "content://test.slice"): MediaDevice {
+        val bluetoothDevice: BluetoothDevice = mock {
+            whenever(getMetadata(any()))
+                .thenReturn(
+                    ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" +
+                            sliceUri +
+                            "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>")
+                        .toByteArray()
+                )
+        }
+        val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+            whenever(device).thenReturn(bluetoothDevice)
+        }
+        return mock<BluetoothMediaDevice> {
+            whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
new file mode 100644
index 0000000..553aed8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.volume.panel.component.anc.domain
+
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceInteractor
+import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+import com.android.systemui.volume.panel.component.anc.sliceViewManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncAvailabilityCriteriaTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AncAvailabilityCriteria
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(mock {})
+
+            underTest = AncAvailabilityCriteria(ancSliceInteractor)
+        }
+    }
+
+    @Test
+    fun noSlice_unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(1, null)
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun hasSlice_available() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+                )
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isTrue()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
new file mode 100644
index 0000000..53f0bc9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.volume.panel.component.anc.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncSliceInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AncSliceInteractor
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            underTest = AncSliceInteractor(ancSliceRepository, testScope.backgroundScope)
+        }
+    }
+
+    @Test
+    fun errorSlice_returnsNull() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = true, hasSliceItem = true)
+                )
+
+                val slice by collectLastValue(underTest.ancSlice)
+                runCurrent()
+
+                assertThat(slice).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun noSliceItem_returnsNull() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = false)
+                )
+
+                val slice by collectLastValue(underTest.ancSlice)
+                runCurrent()
+
+                assertThat(slice).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun sliceItem_noError_returnsSlice() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+                )
+
+                val slice by collectLastValue(underTest.ancSlice)
+                runCurrent()
+
+                assertThat(slice).isNotNull()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
index f4d34f4..8a0dd12 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
@@ -27,7 +27,7 @@
     <com.android.keyguard.KeyguardStatusView
         android:id="@+id/clock"
         android:orientation="vertical"
-        android:layout_width="410dp"
+        android:layout_width="@dimen/keyguard_presentation_width"
         android:layout_height="wrap_content">
         <LinearLayout
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 4e540de..186bd7c 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -173,4 +173,6 @@
     <dimen name="sfps_progress_bar_thickness">6dp</dimen>
     <!-- Padding from the edge of the screen for the progress bar -->
     <dimen name="sfps_progress_bar_padding_from_edge">7dp</dimen>
+
+    <dimen name="keyguard_presentation_width">410dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/drawable/media_squiggly_progress.xml b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
index 9cd3f62..ae797f7 100644
--- a/packages/SystemUI/res/drawable/media_squiggly_progress.xml
+++ b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
@@ -14,4 +14,4 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.media.controls.ui.SquigglyProgress />
\ No newline at end of file
+<com.android.systemui.media.controls.ui.drawable.SquigglyProgress />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml
index 217656da..830c882 100644
--- a/packages/SystemUI/res/drawable/qs_media_background.xml
+++ b/packages/SystemUI/res/drawable/qs_media_background.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.systemui.media.controls.ui.IlluminationDrawable
+<com.android.systemui.media.controls.ui.drawable.IlluminationDrawable
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     systemui:highlight="15"
     systemui:cornerRadius="@dimen/notification_corner_radius" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_light_source.xml b/packages/SystemUI/res/drawable/qs_media_light_source.xml
index 849349a..0b42dba 100644
--- a/packages/SystemUI/res/drawable/qs_media_light_source.xml
+++ b/packages/SystemUI/res/drawable/qs_media_light_source.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.media.controls.ui.LightSourceDrawable
+<com.android.systemui.media.controls.ui.drawable.LightSourceDrawable
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     systemui:rippleMinSize="25dp"
     systemui:rippleMaxSize="135dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 715c869..825ece85 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -24,7 +24,7 @@
     android:clipToPadding="false"
     android:forceHasOverlappingRendering="false"
     android:theme="@style/MediaPlayer">
-    <com.android.systemui.media.controls.ui.MediaScrollView
+    <com.android.systemui.media.controls.ui.view.MediaScrollView
         android:id="@+id/media_carousel_scroller"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -42,7 +42,7 @@
             >
             <!-- QSMediaPlayers will be added here dynamically -->
         </LinearLayout>
-    </com.android.systemui.media.controls.ui.MediaScrollView>
+    </com.android.systemui.media.controls.ui.view.MediaScrollView>
     <com.android.systemui.qs.PageIndicator
         android:id="@+id/media_page_indicator"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
index 0dcd15b..bb8de4c 100644
--- a/packages/SystemUI/res/layout/scene_window_root.xml
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -24,7 +24,7 @@
     android:id="@+id/scene_window_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:fitsSystemWindows="true">
+    android:fitsSystemWindows="false">
 
     <include layout="@layout/super_notification_shade"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 65120a9..5436642 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -608,8 +608,6 @@
     <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
 
     <dimen name="volume_panel_corner_radius">52dp</dimen>
-    <dimen name="volume_panel_bottom_bar_horizontal_padding">24dp</dimen>
-    <dimen name="volume_panel_bottom_bar_bottom_padding">20dp</dimen>
 
     <!-- Size of each item in the ringer selector drawer. -->
     <dimen name="volume_ringer_drawer_item_size">42dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
index dec7d79..630610d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -19,13 +19,22 @@
 import android.app.Presentation
 import android.content.Context
 import android.graphics.Color
+import android.graphics.Rect
 import android.os.Bundle
 import android.view.Display
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.res.R
+import com.android.systemui.shared.clocks.ClockRegistry
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -37,6 +46,8 @@
     @Assisted display: Display,
     context: Context,
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+    private val clockRegistry: ClockRegistry,
+    private val clockEventController: ClockEventController,
 ) :
     Presentation(
         context,
@@ -45,18 +56,126 @@
         WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
     ) {
 
+    private lateinit var rootView: FrameLayout
+    private var clock: View? = null
     private lateinit var keyguardStatusViewController: KeyguardStatusViewController
-    private lateinit var clock: KeyguardStatusView
+    private lateinit var faceController: ClockFaceController
+    private lateinit var clockFrame: FrameLayout
+
+    private val clockChangedListener =
+        object : ClockRegistry.ClockChangeListener {
+            override fun onCurrentClockChanged() {
+                setClock(clockRegistry.createCurrentClock())
+            }
+
+            override fun onAvailableClocksChanged() {}
+        }
+
+    private val layoutChangeListener =
+        object : View.OnLayoutChangeListener {
+            override fun onLayoutChange(
+                view: View,
+                left: Int,
+                top: Int,
+                right: Int,
+                bottom: Int,
+                oldLeft: Int,
+                oldTop: Int,
+                oldRight: Int,
+                oldBottom: Int
+            ) {
+                clock?.let {
+                    faceController.events.onTargetRegionChanged(
+                        Rect(it.left, it.top, it.width, it.height)
+                    )
+                }
+            }
+        }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        if (migrateClocksToBlueprint()) {
+            onCreateV2()
+        } else {
+            onCreate()
+        }
+    }
+
+    fun onCreateV2() {
+        rootView = FrameLayout(getContext(), null)
+        rootView.setClipChildren(false)
+        setContentView(rootView)
+
+        setFullscreen()
+
+        setClock(clockRegistry.createCurrentClock())
+    }
+
+    fun onCreate() {
         setContentView(
             LayoutInflater.from(context)
                 .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
         )
-        val window = window ?: error("no window available.")
 
+        setFullscreen()
+
+        clock = requireViewById(R.id.clock)
+        keyguardStatusViewController =
+            keyguardStatusViewComponentFactory
+                .build(clock as KeyguardStatusView, display)
+                .keyguardStatusViewController
+                .apply {
+                    setDisplayedOnSecondaryDisplay()
+                    init()
+                }
+    }
+
+    override fun onAttachedToWindow() {
+        if (migrateClocksToBlueprint()) {
+            clockRegistry.registerClockChangeListener(clockChangedListener)
+            clockEventController.registerListeners(clock!!)
+
+            faceController.animations.enter()
+        }
+    }
+
+    override fun onDetachedFromWindow() {
+        if (migrateClocksToBlueprint()) {
+            clockEventController.unregisterListeners()
+            clockRegistry.unregisterClockChangeListener(clockChangedListener)
+        }
+
+        super.onDetachedFromWindow()
+    }
+
+    override fun onDisplayChanged() {
+        val window = window ?: error("no window available.")
+        window.getDecorView().requestLayout()
+    }
+
+    private fun setClock(clockController: ClockController) {
+        clock?.removeOnLayoutChangeListener(layoutChangeListener)
+        rootView.removeAllViews()
+
+        faceController = clockController.largeClock
+        clock = faceController.view.also { it.addOnLayoutChangeListener(layoutChangeListener) }
+        rootView.addView(
+            clock,
+            FrameLayout.LayoutParams(
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_presentation_width),
+                WRAP_CONTENT,
+                Gravity.CENTER,
+            )
+        )
+
+        clockEventController.clock = clockController
+        clockEventController.setLargeClockOnSecondaryDisplay(true)
+        faceController.events.onSecondaryDisplayChanged(true)
+    }
+
+    private fun setFullscreen() {
+        val window = window ?: error("no window available.")
         // Logic to make the lock screen fullscreen
         window.decorView.systemUiVisibility =
             (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
@@ -65,16 +184,6 @@
         window.attributes.fitInsetsTypes = 0
         window.isNavigationBarContrastEnforced = false
         window.navigationBarColor = Color.TRANSPARENT
-
-        clock = requireViewById(R.id.clock)
-        keyguardStatusViewController =
-            keyguardStatusViewComponentFactory
-                .build(clock, display)
-                .keyguardStatusViewController
-                .apply {
-                    setDisplayedOnSecondaryDisplay()
-                    init()
-                }
     }
 
     /** [ConnectedDisplayKeyguardPresentation] factory. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 8a6f101..0bb5c17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -17,13 +17,10 @@
 
 import android.app.Presentation;
 import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Rect;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManager;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
-import android.os.Bundle;
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.Log;
@@ -31,20 +28,14 @@
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayInfo;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 
 import androidx.annotation.Nullable;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
@@ -66,10 +57,8 @@
     private final DisplayManager mDisplayService;
     private final DisplayTracker mDisplayTracker;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
-    private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final ConnectedDisplayKeyguardPresentation.Factory
             mConnectedDisplayKeyguardPresentationFactory;
-    private final FeatureFlags mFeatureFlags;
     private final Context mContext;
 
     private boolean mShowing;
@@ -106,18 +95,15 @@
     @Inject
     public KeyguardDisplayManager(Context context,
             Lazy<NavigationBarController> navigationBarControllerLazy,
-            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
             DisplayTracker displayTracker,
             @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             DeviceStateHelper deviceStateHelper,
             KeyguardStateController keyguardStateController,
             ConnectedDisplayKeyguardPresentation.Factory
-                    connectedDisplayKeyguardPresentationFactory,
-            FeatureFlags featureFlags) {
+                    connectedDisplayKeyguardPresentationFactory) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
-        mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
         mDisplayService = mContext.getSystemService(DisplayManager.class);
         mDisplayTracker = displayTracker;
@@ -125,7 +111,6 @@
         mDeviceStateHelper = deviceStateHelper;
         mKeyguardStateController = keyguardStateController;
         mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
-        mFeatureFlags = featureFlags;
     }
 
     private boolean isKeyguardShowable(Display display) {
@@ -197,11 +182,7 @@
     }
 
     Presentation createPresentation(Display display) {
-        if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) {
-            return mConnectedDisplayKeyguardPresentationFactory.create(display);
-        } else {
-            return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
-        }
+        return mConnectedDisplayKeyguardPresentationFactory.create(display);
     }
 
     /**
@@ -347,92 +328,4 @@
                     && mRearDisplayPhysicalAddress.equals(display.getAddress());
         }
     }
-
-
-    @VisibleForTesting
-    static final class KeyguardPresentation extends Presentation {
-        private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
-        private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
-        private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-        private KeyguardClockSwitchController mKeyguardClockSwitchController;
-        private View mClock;
-        private int mUsableWidth;
-        private int mUsableHeight;
-        private int mMarginTop;
-        private int mMarginLeft;
-        Runnable mMoveTextRunnable = new Runnable() {
-            @Override
-            public void run() {
-                int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
-                int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
-                mClock.setTranslationX(x);
-                mClock.setTranslationY(y);
-                mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
-            }
-        };
-
-        KeyguardPresentation(Context context, Display display,
-                KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
-            super(context, display, R.style.Theme_SystemUI_KeyguardPresentation,
-                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-            mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
-            setCancelable(false);
-        }
-
-        @Override
-        public void cancel() {
-            // Do not allow anything to cancel KeyguardPresentation except KeyguardDisplayManager.
-        }
-
-        @Override
-        public void onDetachedFromWindow() {
-            mClock.removeCallbacks(mMoveTextRunnable);
-        }
-
-        @Override
-        public void onDisplayChanged() {
-            updateBounds();
-            getWindow().getDecorView().requestLayout();
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            updateBounds();
-
-            setContentView(LayoutInflater.from(getContext())
-                    .inflate(R.layout.keyguard_presentation, null));
-
-            // Logic to make the lock screen fullscreen
-            getWindow().getDecorView().setSystemUiVisibility(
-                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-            getWindow().getAttributes().setFitInsetsTypes(0 /* types */);
-            getWindow().setNavigationBarContrastEnforced(false);
-            getWindow().setNavigationBarColor(Color.TRANSPARENT);
-
-            mClock = findViewById(R.id.clock);
-
-            // Avoid screen burn in
-            mClock.post(mMoveTextRunnable);
-
-            mKeyguardClockSwitchController = mKeyguardStatusViewComponentFactory
-                    .build(findViewById(R.id.clock), getDisplay())
-                    .getKeyguardClockSwitchController();
-
-            mKeyguardClockSwitchController.setOnlyClock(true);
-            mKeyguardClockSwitchController.init();
-        }
-
-        private void updateBounds() {
-            final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics()
-                    .getBounds();
-            mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100;
-            mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100;
-            mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
-            mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index e8499d3..ca83724 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -143,7 +143,7 @@
 
     private fun notifyCameraActive(info: CameraProtectionInfo) {
         listeners.forEach {
-            it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+            it.onApplyCameraProtection(info.cutoutProtectionPath, info.bounds)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index 6314bd9..9357056 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -23,6 +23,6 @@
     val logicalCameraId: String,
     val physicalCameraId: String?,
     val cutoutProtectionPath: Path,
-    val cutoutBounds: Rect,
+    val bounds: Rect,
     val displayUniqueId: String?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
index aad9341..7309599 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -17,8 +17,12 @@
 package com.android.systemui
 
 import android.content.Context
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.Display
 import android.view.DisplayCutout
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.naturalBounds
 import javax.inject.Inject
 
 @SysUISingleton
@@ -33,15 +37,43 @@
         cameraProtectionLoader.loadCameraProtectionInfoList()
     }
 
-    fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+    /**
+     * Returns the [SysUICutoutInformation] for the current display and the current rotation.
+     *
+     * This means that the bounds of the display cutout and the camera protection will be
+     * adjusted/rotated for the current rotation.
+     */
+    fun cutoutInfoForCurrentDisplayAndRotation(): SysUICutoutInformation? {
         val display = context.display
         val displayCutout: DisplayCutout = display.cutout ?: return null
+        return SysUICutoutInformation(displayCutout, getCameraProtectionForDisplay(display))
+    }
+
+    private fun getCameraProtectionForDisplay(display: Display): CameraProtectionInfo? {
         val displayUniqueId: String? = display.uniqueId
         if (displayUniqueId.isNullOrEmpty()) {
-            return SysUICutoutInformation(displayCutout, cameraProtection = null)
+            return null
         }
-        val cameraProtection: CameraProtectionInfo? =
+        val cameraProtection: CameraProtectionInfo =
             cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
-        return SysUICutoutInformation(displayCutout, cameraProtection)
+                ?: return null
+        val adjustedBoundsForRotation =
+            calculateCameraProtectionBoundsForRotation(display, cameraProtection.bounds)
+        return cameraProtection.copy(bounds = adjustedBoundsForRotation)
+    }
+
+    private fun calculateCameraProtectionBoundsForRotation(
+        display: Display,
+        originalProtectionBounds: Rect,
+    ): Rect {
+        val displayNaturalBounds = display.naturalBounds
+        val rotatedBoundsOut = Rect(originalProtectionBounds)
+        RotationUtils.rotateBounds(
+            /* inOutBounds = */ rotatedBoundsOut,
+            /* parentWidth = */ displayNaturalBounds.width(),
+            /* parentHeight = */ displayNaturalBounds.height(),
+            /* rotation = */ display.rotation
+        )
+        return rotatedBoundsOut
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 25ccc16..357eca3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -93,11 +93,13 @@
         @Override
         public void onSessionEnded() {
             mLastProximityEvent = null;
+            mHistoryTracker.removeBeliefListener(mBeliefListener);
             mClassifiers.forEach(FalsingClassifier::onSessionEnded);
         }
 
         @Override
         public void onSessionStarted() {
+            mHistoryTracker.addBeliefListener(mBeliefListener);
             mClassifiers.forEach(FalsingClassifier::onSessionStarted);
         }
     };
@@ -200,7 +202,6 @@
 
         mDataProvider.addSessionListener(mSessionListener);
         mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
-        mHistoryTracker.addBeliefListener(mBeliefListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 5b1082a..3819e61 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -27,6 +27,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
@@ -39,6 +40,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.BooleanFlowOperators;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
@@ -67,6 +69,7 @@
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+    private final Lazy<CommunalInteractor> mCommunalInteractorLazy;
     private final BatteryController mBatteryController;
     private final DockManager mDockManager;
     private final DelayableExecutor mMainExecutor;
@@ -76,6 +79,7 @@
 
     private int mState;
     private boolean mShowingAod;
+    private boolean mShowingCommunalHub;
     private boolean mScreenOn;
     private boolean mSessionStarted;
     private MotionEvent mPendingDownEvent;
@@ -145,7 +149,8 @@
             @Main DelayableExecutor mainExecutor,
             JavaAdapter javaAdapter,
             SystemClock systemClock,
-            Lazy<SelectedUserInteractor> userInteractor) {
+            Lazy<SelectedUserInteractor> userInteractor,
+            Lazy<CommunalInteractor> communalInteractorLazy) {
         mFalsingDataProvider = falsingDataProvider;
         mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -160,6 +165,7 @@
         mJavaAdapter = javaAdapter;
         mSystemClock = systemClock;
         mUserInteractor = userInteractor;
+        mCommunalInteractorLazy = communalInteractorLazy;
     }
 
     @Override
@@ -176,6 +182,13 @@
                 mShadeInteractorLazy.get().isQsExpanded(),
                 this::onQsExpansionChanged
         );
+        final CommunalInteractor communalInteractor = mCommunalInteractorLazy.get();
+        mJavaAdapter.alwaysCollectFlow(
+                BooleanFlowOperators.INSTANCE.and(
+                        communalInteractor.isCommunalEnabled(),
+                        communalInteractor.isCommunalShowing()),
+                this::onShowingCommunalHubChanged
+        );
 
         mBatteryController.addCallback(mBatteryListener);
         mDockManager.addListener(mDockEventListener);
@@ -205,6 +218,12 @@
         }
     }
 
+    private void onShowingCommunalHubChanged(boolean isShowing) {
+        logDebug("REAL: onShowingCommunalHubChanged(" + isShowing + ")");
+        mShowingCommunalHub = isShowing;
+        updateSessionActive();
+    }
+
     @Override
     public boolean shouldEnforceBouncer() {
         return false;
@@ -333,7 +352,10 @@
     }
 
     private boolean shouldSessionBeActive() {
-        return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
+        return mScreenOn
+                && (mState == StatusBarState.KEYGUARD)
+                && !mShowingAod
+                && !mShowingCommunalHub;
     }
 
     private void updateSessionActive() {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index e0ce3db..c7a47b1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -18,12 +18,14 @@
 
 import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
 
+import static com.android.systemui.Flags.clipboardNoninteractiveOnLockscreen;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
 
 import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
+import android.app.KeyguardManager;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
@@ -57,6 +59,7 @@
     private final Provider<ClipboardOverlayController> mOverlayProvider;
     private final ClipboardToast mClipboardToast;
     private final ClipboardManager mClipboardManager;
+    private final KeyguardManager mKeyguardManager;
     private final UiEventLogger mUiEventLogger;
     private ClipboardOverlay mClipboardOverlay;
 
@@ -65,11 +68,13 @@
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
             ClipboardToast clipboardToast,
             ClipboardManager clipboardManager,
+            KeyguardManager keyguardManager,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager;
+        mKeyguardManager = keyguardManager;
         mUiEventLogger = uiEventLogger;
     }
 
@@ -92,7 +97,9 @@
             return;
         }
 
-        if (!isUserSetupComplete() // user should not access intents from this state
+        // user should not access intents before setup or while device is locked
+        if ((clipboardNoninteractiveOnLockscreen() && mKeyguardManager.isDeviceLocked())
+                || !isUserSetupComplete()
                 || clipData == null // shouldn't happen, but just in case
                 || clipData.getItemCount() == 0) {
             if (shouldShowToast(clipData)) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index 201be51..e2fed6d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.log.dagger.CommunalTableLog
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 636ea42..988393e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -75,7 +75,7 @@
     mediaRepository: CommunalMediaRepository,
     smartspaceRepository: SmartspaceRepository,
     keyguardInteractor: KeyguardInteractor,
-    private val communalSettingsInteractor: CommunalSettingsInteractor,
+    communalSettingsInteractor: CommunalSettingsInteractor,
     private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
     @CommunalLog logBuffer: LogBuffer,
@@ -89,8 +89,7 @@
     val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
 
     /** Whether communal features are enabled. */
-    val isCommunalEnabled: Boolean
-        get() = communalSettingsInteractor.isCommunalEnabled.value
+    val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
 
     /** Whether communal features are enabled and available. */
     val isCommunalAvailable: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
index 2d9dd50..1a323a75 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -47,7 +47,7 @@
     private var communalTutorialIndicatorHandle: DisposableHandle? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!communalInteractor.isCommunalEnabled) {
+        if (!communalInteractor.isCommunalEnabled.value) {
             return
         }
         val padding =
@@ -79,7 +79,7 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!communalInteractor.isCommunalEnabled) {
+        if (!communalInteractor.isCommunalEnabled.value) {
             return
         }
         communalTutorialIndicatorHandle =
@@ -90,7 +90,7 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!communalInteractor.isCommunalEnabled) {
+        if (!communalInteractor.isCommunalEnabled.value) {
             return
         }
         val tutorialIndicatorId = R.id.communal_tutorial_indicator
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index dee7a0c..8a7b5eb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHost
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index efbb11e..bfe751a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.dagger.MediaModule
 import javax.inject.Inject
 import javax.inject.Named
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index febfd4c..fc9a7df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -24,9 +24,9 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java
index 6a72785..bdefc4d 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java
@@ -28,7 +28,7 @@
 import com.android.systemui.complication.dagger.DreamMediaEntryComplicationComponent;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.ui.MediaCarouselController;
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController;
 import com.android.systemui.media.dream.MediaDreamComplication;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
index db2cdfa..231fb2d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.dagger.qualifiers
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import javax.inject.Qualifier
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class NotifInflation
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
new file mode 100644
index 0000000..0482bd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display
+
+import android.graphics.Rect
+import android.view.Display
+import android.view.DisplayInfo
+
+val Display.naturalBounds: Rect
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return Rect(
+            /* left = */ 0,
+            /* top = */ 0,
+            /* right = */ outDisplayInfo.naturalWidth,
+            /* bottom = */ outDisplayInfo.naturalHeight
+        )
+    }
+
+val Display.naturalWidth: Int
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return outDisplayInfo.naturalWidth
+    }
+
+val Display.naturalHeight: Int
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return outDisplayInfo.naturalHeight
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index efbd59f..2fe1dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -19,6 +19,7 @@
 import android.app.Notification
 import android.app.NotificationChannel
 import android.app.NotificationManager
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.content.Context
 import android.util.Log
 import com.android.systemui.CoreStartable
@@ -150,7 +151,7 @@
         val title = "Invalid flag dependencies: ${unmet.size}"
         val details = unmet.joinToString("\n") { it.shortUnmetString() }
         Log.e("FlagDependencies", "$title:\n$details")
-        val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT)
+        val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_DEFAULT)
         val notification =
             Notification.Builder(context, channel.id)
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
@@ -160,7 +161,18 @@
                 .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .build()
         notifManager.createNotificationChannel(channel)
-        notifManager.notify("flags", 0, notification)
+        notifManager.notify(NOTIF_TAG, NOTIF_ID, notification)
+    }
+
+    override fun onCollected(all: List<FlagDependenciesBase.Dependency>) {
+        notifManager.cancel(NOTIF_TAG, NOTIF_ID)
+    }
+
+    companion object {
+        private const val CHANNEL_ID = "FLAGS"
+        private const val CHANNEL_NAME = "Flags"
+        private const val NOTIF_TAG = "FlagDependenciesNotifier"
+        private const val NOTIF_ID = 0
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 56162ae..aae73bc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -324,9 +324,6 @@
     // TODO(b/254512673): Tracking Bug
     @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
 
-    // TODO(b/263272731): Tracking Bug
-    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
-
     // TODO(b/266157412): Tracking Bug
     val MEDIA_RETAIN_SESSIONS = unreleasedFlag("media_retain_sessions")
 
@@ -477,11 +474,6 @@
     val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
         unreleasedFlag("warn_on_blocking_binder_transactions")
 
-    // TODO(b/283071711): Tracking bug
-    @JvmField
-    val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
-        unreleasedFlag("trim_resources_with_background_trim_on_lock")
-
     // TODO:(b/283203305): Tracking bug
     @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index c52ca68..e101b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -32,13 +32,13 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.utils.GlobalWindowManager
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 /**
  * Releases cached resources on allocated by keyguard.
@@ -62,7 +62,7 @@
 
     override fun start() {
         Log.d(LOG_TAG, "Resource trimmer registered.")
-        if (featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+        if (com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
             applicationScope.launch(bgDispatcher) {
                 // We need to wait for the AoD transition (and animation) to complete.
                 // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
@@ -107,19 +107,16 @@
 
     @WorkerThread
     private fun onWakefulnessUpdated(
-            isAsleep: Boolean,
-            isDreaming: Boolean,
-            isDozingFully: Boolean
+        isAsleep: Boolean,
+        isDreaming: Boolean,
+        isDozingFully: Boolean
     ) {
-        if (!featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+        if (!com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
             return
         }
 
         if (DEBUG) {
-            Log.d(
-                LOG_TAG,
-                "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully"
-            )
+            Log.d(LOG_TAG, "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully")
         }
         // There are three scenarios:
         // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false
@@ -129,8 +126,7 @@
         // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases
         //      to 1f
         val dozeDisabledAndScreenOff = isAsleep && !isDreaming
-        val dozeEnabledAndDozeAnimationCompleted =
-                isAsleep && isDreaming && isDozingFully
+        val dozeEnabledAndDozeAnimationCompleted = isAsleep && isDreaming && isDozingFully
         if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
             Trace.beginSection("ResourceTrimmer#trimMemory")
             Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ed48848..dc1f33d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -25,6 +25,7 @@
 import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
+import android.view.View.VISIBLE
 import android.view.ViewGroup
 import android.view.ViewGroup.OnHierarchyChangeListener
 import android.view.ViewPropertyAnimator
@@ -66,6 +67,7 @@
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
 import javax.inject.Provider
+import kotlin.math.min
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
@@ -345,7 +347,7 @@
             }
         }
 
-        onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
+        onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
 
         // Views will be added or removed after the call to bind(). This is needed to avoid many
@@ -405,6 +407,7 @@
 
     private class OnLayoutChange(
         private val viewModel: KeyguardRootViewModel,
+        private val childViews: Map<Int, View>,
         private val burnInParams: MutableStateFlow<BurnInParameters>,
     ) : OnLayoutChangeListener {
         override fun onLayoutChange(
@@ -418,7 +421,7 @@
             oldRight: Int,
             oldBottom: Int
         ) {
-            view.findViewById<View>(R.id.nssl_placeholder)?.let { notificationListPlaceholder ->
+            childViews[R.id.nssl_placeholder]?.let { notificationListPlaceholder ->
                 // After layout, ensure the notifications are positioned correctly
                 viewModel.onNotificationContainerBoundsChanged(
                     notificationListPlaceholder.top.toFloat(),
@@ -426,10 +429,34 @@
                 )
             }
 
-            view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView ->
-                burnInParams.update { current -> current.copy(statusViewTop = statusView.top) }
+            burnInParams.update { current ->
+                current.copy(
+                    minViewY =
+                        if (migrateClocksToBlueprint()) {
+                            // To ensure burn-in doesn't enroach the top inset, get the min top Y
+                            childViews.entries.fold(Int.MAX_VALUE) { currentMin, (viewId, view) ->
+                                min(
+                                    currentMin,
+                                    if (!isUserVisible(view)) {
+                                        Int.MAX_VALUE
+                                    } else {
+                                        view.getTop()
+                                    }
+                                )
+                            }
+                        } else {
+                            childViews[R.id.keyguard_status_view]?.top ?: 0
+                        }
+                )
             }
         }
+
+        private fun isUserVisible(view: View): Boolean {
+            return view.id != R.id.burn_in_layer &&
+                view.visibility == VISIBLE &&
+                view.width > 0 &&
+                view.height > 0
+        }
     }
 
     suspend fun bindAodNotifIconVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 390b39f..6e8605b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -34,7 +34,7 @@
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.KeyguardViewConfigurator
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.shade.NotificationPanelViewController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index b12a8a8..21e9455 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -30,7 +30,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 6fcbf48..8665aab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -158,9 +158,9 @@
                 val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
                 val translationY =
                     if (Flags.migrateClocksToBlueprint()) {
-                        burnInY
+                        max(params.topInset - params.minViewY, burnInY)
                     } else {
-                        max(params.topInset, params.statusViewTop + burnInY) - params.statusViewTop
+                        max(params.topInset, params.minViewY + burnInY) - params.minViewY
                     }
 
                 BurnInModel(
@@ -194,8 +194,8 @@
     val clockControllerProvider: Provider<ClockController>? = null,
     /** System insets that keyguard needs to stay out of */
     val topInset: Int = 0,
-    /** Status view top, without translation added in */
-    val statusViewTop: Int = 0,
+    /** The min y-value of the visible elements on lockscreen */
+    val minViewY: Int = Int.MAX_VALUE,
     /** The current y translation of the view */
     val translationY: () -> Float? = { null }
 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index a0a77fb..c409028 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -25,10 +25,13 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
@@ -39,6 +42,7 @@
 @Inject
 constructor(
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+    shadeInteractor: ShadeInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
 
@@ -73,11 +77,22 @@
     }
 
     val notificationAlpha: Flow<Float> =
-        transitionAnimation.sharedFlow(
-            duration = 500.milliseconds,
-            onStep = { it },
-            onCancel = { 1f },
-        )
+        combine(
+            shadeInteractor.shadeExpansion.map { it > 0f },
+            shadeInteractor.qsExpansion.map { it > 0f },
+            transitionAnimation.sharedFlow(
+                duration = 500.milliseconds,
+                onStep = { it },
+                onCancel = { 1f },
+            ),
+        ) { isShadeExpanded, isQsExpanded, alpha ->
+            if (isShadeExpanded || isQsExpanded) {
+                // One example of this happening is dragging a notification while pulsing on AOD
+                1f
+            } else {
+                alpha
+            }
+        }
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 3511933..921eb66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.graphics.Point
+import android.util.MathUtils
 import android.view.View.VISIBLE
 import com.android.systemui.Flags.newAodTransition
 import com.android.systemui.common.shared.model.NotificationContainerBounds
@@ -33,6 +34,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -43,6 +45,7 @@
 import com.android.systemui.util.ui.toAnimatedValueFlow
 import com.android.systemui.util.ui.zip
 import javax.inject.Inject
+import kotlin.math.max
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -63,7 +66,7 @@
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
@@ -87,6 +90,7 @@
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val aodBurnInViewModel: AodBurnInViewModel,
     private val aodAlphaViewModel: AodAlphaViewModel,
+    private val shadeInteractor: ShadeInteractor,
 ) {
 
     val burnInLayerVisibility: Flow<Int> =
@@ -102,6 +106,16 @@
             .onStart { emit(false) }
             .distinctUntilChanged()
 
+    private val alphaOnShadeExpansion: Flow<Float> =
+        combine(
+                shadeInteractor.qsExpansion,
+                shadeInteractor.shadeExpansion,
+            ) { qsExpansion, shadeExpansion ->
+                // Fade out quickly as the shade expands
+                1f - MathUtils.constrainedMap(0f, 1f, 0f, 0.2f, max(qsExpansion, shadeExpansion))
+            }
+            .distinctUntilChanged()
+
     /** Last point that the root view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
 
@@ -119,10 +133,12 @@
     fun alpha(viewState: ViewStateAccessor): Flow<Float> {
         return combine(
                 communalInteractor.isIdleOnCommunal,
+                keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
                 // The transitions are mutually exclusive, so they are safe to merge to get the last
                 // value emitted by any of them. Do not add flows that cannot make this guarantee.
                 merge(
                         aodAlphaViewModel.alpha,
+                        alphaOnShadeExpansion,
                         keyguardInteractor.dismissAlpha.filterNotNull(),
                         alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
                         aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
@@ -140,11 +156,12 @@
                         primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
                     )
                     .onStart { emit(1f) }
-            ) { isIdleOnCommunal, alpha ->
-                if (isIdleOnCommunal) {
+            ) { isIdleOnCommunal, goneValue, alpha ->
+                if (isIdleOnCommunal || goneValue == 1f) {
                     // Keyguard should not show while the communal hub is fully visible. This check
                     // is added since at the moment, closing the notification shade will cause the
-                    // keyguard alpha to be set back to 1.
+                    // keyguard alpha to be set back to 1. Also ensure keyguard is never visible
+                    // when GONE.
                     0f
                 } else {
                     alpha
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index ac579d6..baf3e64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -333,7 +333,7 @@
     /**
      * Provides a buffer for our connections and disconnections to MediaBrowserService.
      *
-     * See {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}.
+     * See {@link com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser}.
      */
     @Provides
     @SysUISingleton
@@ -345,7 +345,7 @@
     /**
      * Provides a buffer for updates to the media carousel.
      *
-     * See {@link com.android.systemui.media.controls.ui.MediaCarouselController}.
+     * See {@link com.android.systemui.media.controls.ui.controller.MediaCarouselController}.
      */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
index 1c00c93..901559b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -26,7 +26,8 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
index 86a916e..abbfd4f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -26,7 +26,8 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaCarouselController}
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.controls.ui.controller.MediaCarouselController}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
index 98e6556..0239caa 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -26,7 +26,8 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.pipeline.MediaTimeoutLogger}
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.controls.domain.pipeline.MediaTimeoutLogger}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
index dde0ee0..27a6a64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -26,7 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaViewLogger}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.controller.MediaViewLogger}
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
index 789ef40..ad70db5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import javax.inject.Inject
 
 /** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilter.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilter.kt
index 185a783..bc539ef 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilter.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.content.Context
 import android.content.pm.UserInfo
@@ -24,9 +24,9 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 47df3b7..84a9690 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.annotation.SuppressLint
 import android.app.BroadcastOptions
@@ -66,17 +66,17 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaAction
-import com.android.systemui.media.controls.models.player.MediaButton
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.resume.MediaResumeListener
-import com.android.systemui.media.controls.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.controls.util.MediaFlags
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index dcbf670..42d68ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.bluetooth.BluetoothLeBroadcast
 import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -37,8 +37,9 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.LocalMediaManagerFactory
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
index 6a8ffb7..b2a8f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.content.ComponentName
 import android.content.Context
@@ -25,8 +25,8 @@
 import android.util.Log
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -199,9 +199,7 @@
                     packageControllers.put(controller.packageName, tokens)
                 }
         }
-        controllers?.map { TokenId(it.sessionToken) }?.let {
-            tokensWithNotifications.retainAll(it)
-        }
+        controllers?.map { TokenId(it.sessionToken) }?.let { tokensWithNotifications.retainAll(it) }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index ed4eef9..29f3967 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.media.session.MediaController
 import android.media.session.MediaSession
@@ -23,8 +23,8 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutLogger.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutLogger.kt
index 534241e..c50c46a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.media.session.PlaybackState
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaBrowserFactory.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaBrowserFactory.java
index 00620b5..2c45ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaBrowserFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume;
+package com.android.systemui.media.controls.domain.resume;
 
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
index 23ee00d..e4047e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
 
 import android.content.BroadcastReceiver
 import android.content.ComponentName
@@ -34,9 +34,9 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowser.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowser.java
index ceaccaf..b2960cd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume;
+package com.android.systemui.media.controls.domain.resume;
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserFactory.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserFactory.java
index e374191..50eb776 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume;
+package com.android.systemui.media.controls.domain.resume;
 
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserLogger.kt
index 888b9c7..ce2a080 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
 
 import android.content.ComponentName
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
index 5caa27f..4fa7cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.shared.model
 
 import android.app.PendingIntent
 import android.graphics.drawable.Drawable
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
index ae03f27..52c605f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.shared.model
 
 import android.app.smartspace.SmartspaceAction
 import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaDataProvider.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaDataProvider.kt
index cacb3e2..8726d81 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaDataProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.shared.model
 
 import android.app.smartspace.SmartspaceTarget
 import android.util.Log
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandler.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandler.kt
index f5cc043..8258059 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.Drawable
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
index 952f9b8..21407f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import android.animation.ArgbEvaluator
 import android.animation.ValueAnimator
@@ -27,7 +27,7 @@
 import com.android.internal.R
 import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.Utils
-import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
 import com.android.systemui.surfaceeffects.ripple.MultiRippleController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MediaColorSchemes.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MediaColorSchemes.kt
index 2a8362b..3c57c83 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MediaColorSchemes.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import com.android.systemui.monet.ColorScheme
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandler.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandler.kt
index 1cdcf5e..98202c5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index 8d918e7..34f7c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.binder
 
 import android.animation.Animator
 import android.animation.ObjectAnimator
@@ -24,7 +24,9 @@
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.TraceStateLogger
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.res.R
 
 private const val TAG = "SeekBarObserver"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index c629337..9206af2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.content.Context
 import android.content.res.Configuration
@@ -31,6 +31,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.dagger.MediaModule.KEYGUARD
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerLogger.kt
index 0dd4b58..c0d9dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.view.ViewGroup
 import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 992eeca..b721236 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.app.PendingIntent
 import android.content.Context
@@ -46,12 +46,15 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaScrollView
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.SmallHash
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index 3dc0000..ebf1c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 7cf18ba..e8ad4d3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui;
+package com.android.systemui.media.controls.ui.controller;
 
 import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
 
 import static com.android.systemui.Flags.legacyLeAudioSharing;
-import static com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
+import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
@@ -90,17 +90,21 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.media.controls.models.GutsViewHolder;
-import com.android.systemui.media.controls.models.player.MediaAction;
-import com.android.systemui.media.controls.models.player.MediaButton;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.player.MediaDeviceData;
-import com.android.systemui.media.controls.models.player.MediaViewHolder;
-import com.android.systemui.media.controls.models.player.SeekBarObserver;
-import com.android.systemui.media.controls.models.player.SeekBarViewModel;
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder;
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaAction;
+import com.android.systemui.media.controls.shared.model.MediaButton;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.MediaDeviceData;
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
+import com.android.systemui.media.controls.ui.animation.AnimationBindHandler;
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition;
+import com.android.systemui.media.controls.ui.animation.MediaColorSchemesKt;
+import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler;
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver;
+import com.android.systemui.media.controls.ui.view.GutsViewHolder;
+import com.android.systemui.media.controls.ui.view.MediaViewHolder;
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
 import com.android.systemui.media.controls.util.MediaDataUtils;
 import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.controls.util.MediaUiEventLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 35e0271..3b989d9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -42,7 +42,8 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
index 1f711cf..8660d12 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.util.animation.MeasurementOutput
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 962764c..ad7990b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.content.Context
 import android.content.res.Configuration
@@ -22,10 +22,11 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
 import com.android.app.tracing.traceSection
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
index 3ff23159..1514db3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/IlluminationDrawable.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/IlluminationDrawable.kt
index 5aa6824..260d300 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/IlluminationDrawable.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/LightSourceDrawable.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/LightSourceDrawable.kt
index 6ee072d..e4ce135 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/LightSourceDrawable.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgress.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgress.kt
index 47df021..c417fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgress.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/GutsViewHolder.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/view/GutsViewHolder.kt
index f5f5d38..a667c58 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/GutsViewHolder.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models
+package com.android.systemui.media.controls.ui.view
 
 import android.content.res.ColorStateList
 import android.util.Log
@@ -22,11 +22,11 @@
 import android.view.ViewGroup
 import android.widget.ImageButton
 import android.widget.TextView
-import com.android.systemui.res.R
-import com.android.systemui.media.controls.ui.accentPrimaryFromScheme
-import com.android.systemui.media.controls.ui.surfaceFromScheme
-import com.android.systemui.media.controls.ui.textPrimaryFromScheme
+import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme
+import com.android.systemui.media.controls.ui.animation.surfaceFromScheme
+import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme
 import com.android.systemui.monet.ColorScheme
+import com.android.systemui.res.R
 
 /**
  * A view holder for the guts menu of a media player. The guts are shown when the user long-presses
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index 038582c..c033e46 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
 
 import android.graphics.Outline
 import android.util.MathUtils
@@ -31,6 +31,7 @@
 import com.android.settingslib.Utils
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
+import com.android.systemui.media.controls.ui.controller.MediaControlPanel
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 437218f..d92168b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
 
 import android.graphics.Rect
 import android.util.ArraySet
 import android.view.View
 import android.view.View.OnAttachStateChangeListener
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager
+import com.android.systemui.media.controls.ui.controller.MediaLocation
 import com.android.systemui.util.animation.DisappearParameters
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.MeasurementOutput
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
index 10512f1..b625908 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
 
 import android.content.Context
 import android.os.SystemClock
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
index 898eacf..35309ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.view
 
 import android.view.LayoutInflater
 import android.view.View
@@ -25,7 +25,6 @@
 import android.widget.TextView
 import androidx.constraintlayout.widget.Barrier
 import com.android.internal.widget.CachingIconView
-import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.res.R
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
 import com.android.systemui.surfaceeffects.ripple.MultiRippleView
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
index 8ac8a2d..2d028d0213 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.ui.view
 
 import android.view.LayoutInflater
 import android.view.View
@@ -23,9 +23,8 @@
 import android.widget.SeekBar
 import android.widget.TextView
 import com.android.internal.widget.CachingIconView
+import com.android.systemui.media.controls.ui.drawable.IlluminationDrawable
 import com.android.systemui.res.R
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.ui.IlluminationDrawable
 import com.android.systemui.util.animation.TransitionLayout
 
 private const val TAG = "RecommendationViewHolder"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index 13d743f..cef1e69 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.viewmodel
 
 import android.media.MediaMetadata
 import android.media.session.MediaController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 1d3cfd2..5d113a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.util
 
 import android.content.Context
 import com.android.settingslib.bluetooth.LocalBluetoothManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 16a703a..f8c816c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -21,9 +21,9 @@
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaLocation
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaLocation
 import com.android.systemui.res.R
 import java.lang.IllegalArgumentException
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 8f752e5..d84e5dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -19,10 +19,10 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.media.controls.ui.MediaHostStatesManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
index b4153d7..7f6398b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -21,9 +21,9 @@
 
 import android.widget.FrameLayout;
 
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.media.controls.ui.MediaHostState;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHostState;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 08c626c..88a5f78 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -27,9 +27,9 @@
 import com.android.systemui.complication.DreamMediaEntryComplication;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
 
 import javax.inject.Inject;
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 8a565fa..03bc935 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -26,8 +26,4 @@
 class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
     /** */
     fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
-
-    /** Check whether the flag for the receiver success state is enabled. */
-    fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
-        featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 6e9485e..416eae1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -266,8 +266,7 @@
         // translation animation.
         bounceAnimator.removeAllUpdateListeners()
         bounceAnimator.cancel()
-        if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
-                mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
+        if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name) {
             rippleController.expandToSuccessState(rippleView, onAnimationEnd)
             animateViewTranslationAndFade(
                 iconContainerView,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 8ff0e36..7413362 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -51,7 +51,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -207,6 +207,9 @@
         mFooterActionsViewBinder = footerActionsViewBinder;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
         mSceneContainerFlags = sceneContainerFlags;
+        if (mSceneContainerFlags.isEnabled()) {
+            mStatusBarState = StatusBarState.SHADE;
+        }
     }
 
     /**
@@ -506,10 +509,20 @@
         }
     }
 
-    private boolean isKeyguardState() {
-        // We want the freshest state here since otherwise we'll have some weirdness if earlier
-        // listeners trigger updates
-        return mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD;
+    @VisibleForTesting
+    boolean isKeyguardState() {
+        if (mSceneContainerFlags.isEnabled()) {
+            return false;
+        } else {
+            // We want the freshest state here since otherwise we'll have some weirdness if earlier
+            // listeners trigger updates
+            return mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD;
+        }
+    }
+
+    @VisibleForTesting
+    int getStatusBarState() {
+        return mStatusBarState;
     }
 
     private void updateShowCollapsedOnKeyguard() {
@@ -562,15 +575,17 @@
     }
 
     private void setKeyguardShowing(boolean keyguardShowing) {
-        if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
-        mLastQSExpansion = -1;
+        if (!mSceneContainerFlags.isEnabled()) {
+            if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
+            mLastQSExpansion = -1;
 
-        if (mQSAnimator != null) {
-            mQSAnimator.setOnKeyguard(keyguardShowing);
+            if (mQSAnimator != null) {
+                mQSAnimator.setOnKeyguard(keyguardShowing);
+            }
+
+            mFooter.setKeyguardShowing(keyguardShowing);
+            updateQsState();
         }
-
-        mFooter.setKeyguardShowing(keyguardShowing);
-        updateQsState();
     }
 
     @Override
@@ -971,7 +986,7 @@
 
     @Override
     public void onStateChanged(int newState) {
-        if (newState == mStatusBarState) {
+        if (mSceneContainerFlags.isEnabled() || newState == mStatusBarState) {
             return;
         }
         mStatusBarState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index c3f5086..2440651 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -27,9 +27,9 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.media.controls.ui.MediaHostState;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHostState;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 1c37510f..975c871 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -32,7 +32,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index f278dce..a8e88da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -25,8 +25,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 4bad45f..16aa99e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesResourceRepository
 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredBroadcastRepository
 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
@@ -81,6 +83,11 @@
         impl: QSSettingsRestoredBroadcastRepository
     ): QSSettingsRestoredRepository
 
+    @Binds
+    abstract fun provideMinimumTilesRepository(
+        impl: MinimumTilesResourceRepository
+    ): MinimumTilesRepository
+
     companion object {
         /**
          * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
new file mode 100644
index 0000000..3a005c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Provides the minimum number of tiles required in QS. The default number of tiles should be at
+ * least this many.
+ */
+interface MinimumTilesRepository {
+    val minNumberOfTiles: Int
+}
+
+/**
+ * Minimum number of tiles using the corresponding resource. The value will be read once upon
+ * creation, as it's not expected to change.
+ */
+@SysUISingleton
+class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :
+    MinimumTilesRepository {
+    override val minNumberOfTiles: Int =
+        resources.getInteger(R.integer.quick_settings_min_num_tiles)
+}
+
+/** Provides a fixed minimum number of tiles. */
+class MinimumTilesFixedRepository(override val minNumberOfTiles: Int = 0) : MinimumTilesRepository
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 00ea0b5..214e9f0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -19,12 +19,12 @@
 import android.annotation.UserIdInt
 import android.content.res.Resources
 import android.util.SparseArray
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.RetailModeRepository
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -68,6 +68,9 @@
 
     suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>)
 
+    /** Prepend the default list of tiles to the current set of tiles */
+    suspend fun prependDefault(@UserIdInt userId: Int)
+
     companion object {
         /** Position to indicate the end of the list */
         const val POSITION_AT_END = -1
@@ -152,6 +155,12 @@
             ?.reconcileRestore(restoreData, currentAutoAdded)
     }
 
+    override suspend fun prependDefault(
+        userId: Int,
+    ) {
+        userTileRepositories.get(userId)?.prependDefault()
+    }
+
     companion object {
         private const val DELIMITER = TilesSettingConverter.DELIMITER
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 152fd0f..8ad5cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -50,9 +50,8 @@
     private val defaultTiles: List<TileSpec>
         get() = defaultTilesRepository.defaultTiles
 
-    private val changeEvents = MutableSharedFlow<ChangeAction>(
-        extraBufferCapacity = CHANGES_BUFFER_SIZE
-    )
+    private val changeEvents =
+        MutableSharedFlow<ChangeAction>(extraBufferCapacity = CHANGES_BUFFER_SIZE)
 
     private lateinit var _tiles: StateFlow<List<TileSpec>>
 
@@ -163,6 +162,10 @@
         changeEvents.emit(RestoreTiles(restoreData, currentAutoAdded))
     }
 
+    suspend fun prependDefault() {
+        changeEvents.emit(PrependDefault(defaultTiles))
+    }
+
     sealed interface ChangeAction {
         fun apply(currentTiles: List<TileSpec>): List<TileSpec>
     }
@@ -199,6 +202,12 @@
         }
     }
 
+    private data class PrependDefault(val defaultTiles: List<TileSpec>) : ChangeAction {
+        override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+            return defaultTiles + currentTiles
+        }
+    }
+
     private data class RestoreTiles(
         val restoreData: RestoreData,
         val currentAutoAdded: Set<TileSpec>,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 957cb1e..61896f0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.external.TileServiceKey
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.domain.model.TileModel
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
@@ -131,6 +132,7 @@
     private val tileSpecRepository: TileSpecRepository,
     private val installedTilesComponentRepository: InstalledTilesComponentRepository,
     private val userRepository: UserRepository,
+    private val minimumTilesRepository: MinimumTilesRepository,
     private val customTileStatePersister: CustomTileStatePersister,
     private val newQSTileFactory: Lazy<NewQSTileFactory>,
     private val tileFactory: QSFactory,
@@ -255,17 +257,23 @@
                         val resolvedSpecs = newTileMap.keys.toList()
                         specsToTiles.clear()
                         specsToTiles.putAll(newTileMap)
-                        _currentSpecsAndTiles.value =
+                        val newResolvedTiles =
                             newTileMap
                                 .filter { it.value is TileOrNotInstalled.Tile }
                                 .map {
                                     TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
                                 }
+
+                        _currentSpecsAndTiles.value = newResolvedTiles
                         logger.logTilesNotInstalled(
                             newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
                             newUser
                         )
-                        if (resolvedSpecs != newTileList) {
+                        if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) {
+                            // We ended up with not enough tiles (some may be not installed).
+                            // Prepend the default set of tiles
+                            launch { tileSpecRepository.prependDefault(currentUser.value) }
+                        } else if (resolvedSpecs != newTileList) {
                             // There were some tiles that couldn't be created. Change the value in
                             // the
                             // repository
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6e4f72d..72a5c46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -84,18 +84,36 @@
      */
     val qsHeight: Int
 
-    sealed class State(
-        val isVisible: Boolean,
-        val expansion: Float,
-    ) {
-        data object CLOSED : State(false, 0f)
-        data class Expanding(val progress: Float) : State(true, progress)
+    sealed interface State {
+
+        val isVisible: Boolean
+        val expansion: Float
+        val squishiness: Float
+
+        data object CLOSED : State {
+            override val isVisible = false
+            override val expansion = 0f
+            override val squishiness = 1f
+        }
+
+        /** State for expanding between QQS and QS */
+        data class Expanding(override val expansion: Float) : State {
+            override val isVisible = true
+            override val squishiness = 1f
+        }
+
+        /** State for appearing QQS from Lockscreen or Gone */
+        data class Unsquishing(override val squishiness: Float) : State {
+            override val isVisible = true
+            override val expansion = 0f
+        }
 
         companion object {
             // These are special cases of the expansion.
             val QQS = Expanding(0f)
             val QS = Expanding(1f)
 
+            /** Collapsing from QS to QQS. [progress] is 0f in QS and 1f in QQS. */
             fun Collapsing(progress: Float) = Expanding(1f - progress)
         }
     }
@@ -232,7 +250,7 @@
         setQsVisible(state.isVisible)
         setExpanded(state.isVisible)
         setListening(state.isVisible)
-        setQsExpansion(state.expansion, 1f, 0f, 1f)
-        setTransitionToFullShadeProgress(false, 1f, 1f)
+        setQsExpansion(state.expansion, 1f, 0f, state.squishiness)
+        setTransitionToFullShadeProgress(false, 1f, state.squishiness)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 4ccb18f..8408c51 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
 import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
 import dagger.Module
 import dagger.Provides
@@ -45,6 +46,7 @@
             sceneContainer() && // mainAconfigFlag
                 keyguardBottomAreaRefactor() &&
                 migrateClocksToBlueprint() &&
+                ComposeLockscreen.isEnabled &&
                 MediaInSceneContainerFlag.isEnabled &&
                 // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
                 ComposeFacade.isComposeAvailable()
@@ -65,6 +67,7 @@
         sequenceOf(
             FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
             FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()),
+            ComposeLockscreen.token,
             MediaInSceneContainerFlag.token,
             // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
         )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 7cff7ff..4c2c979 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,8 +25,8 @@
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.core.view.updateMargins
-import com.android.systemui.res.R
 import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.res.R
 
 /** A view that can serve as the root of the main SysUI window. */
 open class WindowRootView(
@@ -71,6 +71,7 @@
 
     override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
         val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
+        val displayCutout = rootWindowInsets.displayCutout
         if (fitsSystemWindows) {
             val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
 
@@ -78,22 +79,23 @@
             if (paddingChanged) {
                 setPadding(0, 0, 0, 0)
             }
+
+            val pairInsets: Pair<Int, Int> =
+                layoutInsetsController.getinsets(windowInsets, displayCutout)
+            leftInset = pairInsets.first
+            rightInset = pairInsets.second
+            applyMargins()
         } else {
             val changed =
                 paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
             if (changed) {
                 setPadding(0, 0, 0, 0)
             }
-        }
-        leftInset = 0
-        rightInset = 0
 
-        val displayCutout = rootWindowInsets.displayCutout
-        val pairInsets: Pair<Int, Int> =
-            layoutInsetsController.getinsets(windowInsets, displayCutout)
-        leftInset = pairInsets.first
-        rightInset = pairInsets.second
-        applyMargins()
+            leftInset = 0
+            rightInset = 0
+        }
+
         return windowInsets
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95d9bc4..7f06778 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -149,9 +149,9 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index f7fed53..8f9cef3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -60,6 +60,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
@@ -506,7 +507,7 @@
 
     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (mWindowRootView != null
+        if (!SceneContainerFlag.isEnabled() && mWindowRootView != null
                 && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
             mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
             mWindowRootView.requestApplyInsets();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index f7b9e4e..e7f9700 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -71,8 +71,8 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.res.R;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index d393f0d..4054a86 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.shade
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.data.repository.PrivacyChipRepository
+import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
@@ -61,4 +63,8 @@
     abstract fun bindsShadeAnimationInteractor(
         sai: ShadeAnimationInteractorEmptyImpl
     ): ShadeAnimationInteractor
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 86fdcee..5632766 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.data.repository.PrivacyChipRepository
+import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
 import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
@@ -124,4 +126,8 @@
     abstract fun bindsShadeViewController(
         notificationPanelViewController: NotificationPanelViewController
     ): ShadeViewController
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
new file mode 100644
index 0000000..91c92cc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.shade.data.repository
+
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.safetycenter.SafetyCenterManager
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.privacy.PrivacyConfig
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyItemController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface PrivacyChipRepository {
+    /** Whether or not the Safety Center is enabled. */
+    val isSafetyCenterEnabled: StateFlow<Boolean>
+
+    /** The list of PrivacyItems to be displayed by the privacy chip. */
+    val privacyItems: StateFlow<List<PrivacyItem>>
+
+    /** Whether or not mic & camera indicators are enabled in the device privacy config. */
+    val isMicCameraIndicationEnabled: StateFlow<Boolean>
+
+    /** Whether or not location indicators are enabled in the device privacy config. */
+    val isLocationIndicationEnabled: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class PrivacyChipRepositoryImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    private val privacyConfig: PrivacyConfig,
+    private val privacyItemController: PrivacyItemController,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    broadcastDispatcher: BroadcastDispatcher,
+    private val safetyCenterManager: SafetyCenterManager,
+) : PrivacyChipRepository {
+    override val isSafetyCenterEnabled: StateFlow<Boolean> =
+        broadcastDispatcher
+            .broadcastFlow(
+                filter =
+                    IntentFilter().apply {
+                        addAction(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
+                    },
+                user = UserHandle.SYSTEM,
+                map = { _, _ -> safetyCenterManager.isSafetyCenterEnabled }
+            )
+            .onStart { emit(safetyCenterManager.isSafetyCenterEnabled) }
+            .flowOn(backgroundDispatcher)
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    override val privacyItems: StateFlow<List<PrivacyItem>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : PrivacyItemController.Callback {
+                        override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
+                            trySend(privacyItems)
+                        }
+                    }
+                privacyItemController.addCallback(callback)
+                awaitClose { privacyItemController.removeCallback(callback) }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = emptyList(),
+            )
+
+    override val isMicCameraIndicationEnabled: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : PrivacyConfig.Callback {
+                        override fun onFlagMicCameraChanged(flag: Boolean) {
+                            trySend(flag)
+                        }
+                    }
+                privacyConfig.addCallback(callback)
+                awaitClose { privacyConfig.removeCallback(callback) }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = privacyItemController.micCameraAvailable,
+            )
+
+    override val isLocationIndicationEnabled: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : PrivacyConfig.Callback {
+                        override fun onFlagLocationChanged(flag: Boolean) {
+                            trySend(flag)
+                        }
+                    }
+                privacyConfig.addCallback(callback)
+                awaitClose { privacyConfig.removeCallback(callback) }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = privacyItemController.locationAvailable,
+            )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt
new file mode 100644
index 0000000..4c6c318
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyDialogController
+import com.android.systemui.privacy.PrivacyDialogControllerV2
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.shade.data.repository.PrivacyChipRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class PrivacyChipInteractor
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    private val repository: PrivacyChipRepository,
+    private val privacyDialogController: PrivacyDialogController,
+    private val privacyDialogControllerV2: PrivacyDialogControllerV2,
+    private val deviceProvisionedController: DeviceProvisionedController,
+) {
+    /** The list of PrivacyItems to be displayed by the privacy chip. */
+    val privacyItems: StateFlow<List<PrivacyItem>> = repository.privacyItems
+
+    /** Whether or not mic & camera indicators are enabled in the device privacy config. */
+    val isMicCameraIndicationEnabled: StateFlow<Boolean> = repository.isMicCameraIndicationEnabled
+
+    /** Whether or not location indicators are enabled in the device privacy config. */
+    val isLocationIndicationEnabled: StateFlow<Boolean> = repository.isLocationIndicationEnabled
+
+    /** Whether or not the privacy chip should be visible. */
+    val isChipVisible: StateFlow<Boolean> =
+        privacyItems
+            .map { it.isNotEmpty() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether or not the privacy chip is enabled in the device privacy config. */
+    val isChipEnabled: StateFlow<Boolean> =
+        combine(
+                isMicCameraIndicationEnabled,
+                isLocationIndicationEnabled,
+            ) { micCamera, location ->
+                micCamera || location
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Notifies that the privacy chip was clicked. */
+    fun onPrivacyChipClicked(privacyChip: OngoingPrivacyChip) {
+        if (!deviceProvisionedController.isDeviceProvisioned) return
+
+        if (repository.isSafetyCenterEnabled.value) {
+            privacyDialogControllerV2.showDialog(privacyChip.context, privacyChip)
+        } else {
+            privacyDialogController.showDialog(privacyChip.context)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 314637e..700825d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -25,8 +25,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import java.util.Date
@@ -50,9 +52,9 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     context: Context,
-    sceneInteractor: SceneInteractor,
     mobileIconsInteractor: MobileIconsInteractor,
     val mobileIconsViewModel: MobileIconsViewModel,
+    private val privacyChipInteractor: PrivacyChipInteractor,
     broadcastDispatcher: BroadcastDispatcher,
 ) {
     /** True if there is exactly one mobile connection. */
@@ -64,6 +66,23 @@
             .map { list -> list.map { it.subscriptionId } }
             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), emptyList())
 
+    /** The list of PrivacyItems to be displayed by the privacy chip. */
+    val privacyItems: StateFlow<List<PrivacyItem>> = privacyChipInteractor.privacyItems
+
+    /** Whether or not mic & camera indicators are enabled in the device privacy config. */
+    val isMicCameraIndicationEnabled: StateFlow<Boolean> =
+        privacyChipInteractor.isMicCameraIndicationEnabled
+
+    /** Whether or not location indicators are enabled in the device privacy config. */
+    val isLocationIndicationEnabled: StateFlow<Boolean> =
+        privacyChipInteractor.isLocationIndicationEnabled
+
+    /** Whether or not the privacy chip should be visible. */
+    val isPrivacyChipVisible: StateFlow<Boolean> = privacyChipInteractor.isChipVisible
+
+    /** Whether or not the privacy chip is enabled in the device privacy config. */
+    val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
+
     private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
     private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
     private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
@@ -97,6 +116,11 @@
         applicationScope.launch { updateDateTexts(false) }
     }
 
+    /** Notifies that the privacy chip was clicked. */
+    fun onPrivacyChipClicked(privacyChip: OngoingPrivacyChip) {
+        privacyChipInteractor.onPrivacyChipClicked(privacyChip)
+    }
+
     private fun updateDateTexts(invalidateFormats: Boolean) {
         if (invalidateFormats) {
             longerDateFormat.value = getFormatFromPattern(longerPattern)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 9af2d58..abdfa53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
new file mode 100644
index 0000000..384acc4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.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.slice
+
+import android.net.Uri
+import androidx.slice.Slice
+import androidx.slice.SliceViewManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+/**
+ * Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the
+ * provided Uri. This can change overtime because of external changes (like device being
+ * connected/disconnected).
+ */
+fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> =
+    ConflatedCallbackFlow.conflatedCallbackFlow {
+        val callback = SliceViewManager.SliceCallback { launch { send(it) } }
+
+        val slice = bindSlice(sliceUri)
+        send(slice)
+        registerSliceCallback(sliceUri, callback)
+        awaitClose { unregisterSliceCallback(sliceUri, callback) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
index 0b470c1..9f098e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
@@ -4,7 +4,7 @@
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeLockscreenInteractor
 import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 1dbd87e..a59d753 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
@@ -68,7 +68,7 @@
     private val mediaHierarchyManager: MediaHierarchyManager,
     private val scrimTransitionController: LockscreenShadeScrimTransitionController,
     private val keyguardTransitionControllerFactory:
-        LockscreenShadeKeyguardTransitionController.Factory,
+    LockscreenShadeKeyguardTransitionController.Factory,
     private val depthController: NotificationShadeDepthController,
     private val context: Context,
     private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 9c4625e..d465973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -30,9 +30,9 @@
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index f6d99bd..f960fca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -33,7 +33,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 16af9d9..072f56d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
-import static com.android.systemui.media.controls.pipeline.MediaDataManagerKt.isMediaNotification;
+import static com.android.systemui.media.controls.domain.pipeline.MediaDataManagerKt.isMediaNotification;
 
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 913d5f6..e288e85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -43,7 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.NotifInflation;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.InflationTask;
@@ -82,7 +82,7 @@
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotifRemoteViewCache mRemoteViewCache;
     private final ConversationNotificationProcessor mConversationProcessor;
-    private final Executor mBgExecutor;
+    private final Executor mInflationExecutor;
     private final SmartReplyStateInflater mSmartReplyStateInflater;
     private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
     private final NotificationContentInflaterLogger mLogger;
@@ -93,7 +93,7 @@
             NotificationRemoteInputManager remoteInputManager,
             ConversationNotificationProcessor conversationProcessor,
             MediaFeatureFlag mediaFeatureFlag,
-            @Background Executor bgExecutor,
+            @NotifInflation Executor inflationExecutor,
             SmartReplyStateInflater smartRepliesInflater,
             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
             NotificationContentInflaterLogger logger) {
@@ -101,7 +101,7 @@
         mRemoteInputManager = remoteInputManager;
         mConversationProcessor = conversationProcessor;
         mIsMediaInQS = mediaFeatureFlag.getEnabled();
-        mBgExecutor = bgExecutor;
+        mInflationExecutor = inflationExecutor;
         mSmartReplyStateInflater = smartRepliesInflater;
         mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
         mLogger = logger;
@@ -138,7 +138,7 @@
         cancelContentViewFrees(row, contentToBind);
 
         AsyncInflationTask task = new AsyncInflationTask(
-                mBgExecutor,
+                mInflationExecutor,
                 mInflateSynchronously,
                 /* reInflateFlags = */ contentToBind,
                 mRemoteViewCache,
@@ -157,7 +157,7 @@
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
-            task.executeOnExecutor(mBgExecutor);
+            task.executeOnExecutor(mInflationExecutor);
         }
     }
 
@@ -208,7 +208,7 @@
         }
 
         apply(
-                mBgExecutor,
+                mInflationExecutor,
                 inflateSynchronously,
                 result,
                 reInflateFlags,
@@ -416,7 +416,7 @@
     }
 
     private static CancellationSignal apply(
-            Executor bgExecutor,
+            Executor inflationExecutor,
             boolean inflateSynchronously,
             InflationProgress result,
             @InflationFlag int reInflateFlags,
@@ -447,7 +447,7 @@
                 }
             };
             logger.logAsyncTaskProgress(entry, "applying contracted view");
-            applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
+            applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, flag,
                     remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
                     privateLayout, privateLayout.getContractedChild(),
                     privateLayout.getVisibleWrapper(
@@ -474,11 +474,10 @@
                     }
                 };
                 logger.logAsyncTaskProgress(entry, "applying expanded view");
-                applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
-                        remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags,
+                        flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getExpandedChild(),
-                        privateLayout.getVisibleWrapper(
-                                NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
+                        privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), runningInflations,
                         applyCallback, logger);
             }
         }
@@ -502,11 +501,10 @@
                     }
                 };
                 logger.logAsyncTaskProgress(entry, "applying heads up view");
-                applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
-                        remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags,
+                        flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getHeadsUpChild(),
-                        privateLayout.getVisibleWrapper(
-                                VISIBLE_TYPE_HEADSUP), runningInflations,
+                        privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations,
                         applyCallback, logger);
             }
         }
@@ -529,7 +527,7 @@
                 }
             };
             logger.logAsyncTaskProgress(entry, "applying public view");
-            applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
+            applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, flag,
                     remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
                     publicLayout, publicLayout.getContractedChild(),
                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
@@ -551,7 +549,7 @@
 
     @VisibleForTesting
     static void applyRemoteView(
-            Executor bgExecutor,
+            Executor inflationExecutor,
             boolean inflateSynchronously,
             final InflationProgress result,
             final @InflationFlag int reInflateFlags,
@@ -655,14 +653,14 @@
             cancellationSignal = newContentView.applyAsync(
                     result.packageContext,
                     parentLayout,
-                    bgExecutor,
+                    inflationExecutor,
                     listener,
                     remoteViewClickHandler);
         } else {
             cancellationSignal = newContentView.reapplyAsync(
                     result.packageContext,
                     existingView,
-                    bgExecutor,
+                    inflationExecutor,
                     listener,
                     remoteViewClickHandler);
         }
@@ -918,7 +916,7 @@
         private final boolean mUsesIncreasedHeadsUpHeight;
         private final @InflationFlag int mReInflateFlags;
         private final NotifRemoteViewCache mRemoteViewCache;
-        private final Executor mBgExecutor;
+        private final Executor mInflationExecutor;
         private ExpandableNotificationRow mRow;
         private Exception mError;
         private RemoteViews.InteractionHandler mRemoteViewClickHandler;
@@ -930,7 +928,7 @@
         private final NotificationContentInflaterLogger mLogger;
 
         private AsyncInflationTask(
-                Executor bgExecutor,
+                Executor inflationExecutor,
                 boolean inflateSynchronously,
                 @InflationFlag int reInflateFlags,
                 NotifRemoteViewCache cache,
@@ -948,7 +946,7 @@
                 NotificationContentInflaterLogger logger) {
             mEntry = entry;
             mRow = row;
-            mBgExecutor = bgExecutor;
+            mInflationExecutor = inflationExecutor;
             mInflateSynchronously = inflateSynchronously;
             mReInflateFlags = reInflateFlags;
             mRemoteViewCache = cache;
@@ -1067,7 +1065,7 @@
             if (mError == null) {
                 // Logged in detail in apply.
                 mCancellationSignal = apply(
-                        mBgExecutor,
+                        mInflationExecutor,
                         mInflateSynchronously,
                         result,
                         mReInflateFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index cfc433a..d269eda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,7 +19,7 @@
 import android.util.Log
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 715505d..8dfac86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -74,7 +74,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 30708b7..2d9c63e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 77e146b..5191053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -91,10 +91,10 @@
                     if (!sceneContainerFlags.flexiNotifsEnabled()) {
                         launch {
                             // Only temporarily needed, until flexi notifs go live
-                            viewModel.shadeCollpaseFadeIn.collect { fadeIn ->
+                            viewModel.shadeCollapseFadeIn.collect { fadeIn ->
                                 if (fadeIn) {
                                     android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
-                                        duration = 350
+                                        duration = 250
                                         addUpdateListener { animation ->
                                             controller.setMaxAlphaForExpansion(
                                                 animation.getAnimatedFraction()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 6b949a3..052e35c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -133,6 +133,21 @@
             .distinctUntilChanged()
             .onStart { emit(false) }
 
+    /**
+     * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
+     * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
+     * before the other.
+     */
+    private val isShadeLocked: Flow<Boolean> =
+        combine(
+                keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
+                shadeInteractor.qsExpansion.map { it > 0f },
+                shadeInteractor.shadeExpansion.map { it > 0f },
+            ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
+                isShadeLocked && (isQsExpanded || isShadeExpanded)
+            }
+            .distinctUntilChanged()
+
     val shadeCollapseFadeInComplete = MutableStateFlow(false)
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -190,7 +205,7 @@
             )
 
     /** Are we purely on the glanceable hub without the shade/qs? */
-    internal val isOnGlanceableHubWithoutShade: Flow<Boolean> =
+    val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
                 communalInteractor.isIdleOnCommunal,
                 // Shade with notifications
@@ -208,12 +223,12 @@
             )
 
     /** Fade in only for use after the shade collapses */
-    val shadeCollpaseFadeIn: Flow<Boolean> =
+    val shadeCollapseFadeIn: Flow<Boolean> =
         flow {
                 while (currentCoroutineContext().isActive) {
                     emit(false)
                     // Wait for shade to be fully expanded
-                    keyguardInteractor.statusBarState.first { it == SHADE_LOCKED }
+                    isShadeLocked.first { it }
                     // ... and then for it to be collapsed
                     isOnLockscreenWithoutShade.first { it }
                     emit(true)
@@ -330,16 +345,16 @@
                 // shade expansion or swipe to dismiss
                 combineTransform(
                     isOnLockscreenWithoutShade,
-                    shadeCollpaseFadeIn,
+                    shadeCollapseFadeIn,
                     alphaForShadeAndQsExpansion,
                     keyguardInteractor.dismissAlpha,
                 ) {
                     isOnLockscreenWithoutShade,
-                    shadeCollpaseFadeIn,
+                    shadeCollapseFadeIn,
                     alphaForShadeAndQsExpansion,
                     dismissAlpha ->
                     if (isOnLockscreenWithoutShade) {
-                        if (!shadeCollpaseFadeIn && dismissAlpha != null) {
+                        if (!shadeCollapseFadeIn && dismissAlpha != null) {
                             emit(dismissAlpha)
                         }
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 323ab80..613efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -185,7 +185,7 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
             val displayCutout = sysUICutout?.cutout
             val key = getCacheKey(rotation, displayCutout)
 
@@ -227,7 +227,7 @@
      */
     @JvmOverloads
     fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
-        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
         val displayCutout = sysUICutout?.cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key]
@@ -528,7 +528,7 @@
     var leftMargin = minLeft
     var rightMargin = minRight
     for (cutoutRect in cutoutRects) {
-        val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+        val protectionRect = sysUICutout.cameraProtection?.bounds
         val actualCutoutRect =
             if (protectionRect?.intersects(cutoutRect) == true) {
                 rectUnion(cutoutRect, protectionRect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 1c33d3f..bef6b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -18,15 +18,22 @@
 
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
@@ -42,25 +49,44 @@
     interactor: DeviceBasedSatelliteInteractor,
     @Application scope: CoroutineScope,
     airplaneModeRepository: AirplaneModeRepository,
+    @OemSatelliteInputLog logBuffer: LogBuffer,
 ) {
-    private val shouldShowIcon: StateFlow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { allOos ->
-                if (!allOos) {
-                    flowOf(false)
+    private val shouldShowIcon: Flow<Boolean> =
+        interactor.areAllConnectionsOutOfService.flatMapLatest { allOos ->
+            if (!allOos) {
+                flowOf(false)
+            } else {
+                combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
+                    isSatelliteAllowed,
+                    isAirplaneMode ->
+                    isSatelliteAllowed && !isAirplaneMode
+                }
+            }
+        }
+
+    // This adds a 10 seconds delay before showing the icon
+    private val shouldActuallyShowIcon: StateFlow<Boolean> =
+        shouldShowIcon
+            .distinctUntilChanged()
+            .flatMapLatest { shouldShow ->
+                if (shouldShow) {
+                    logBuffer.log(
+                        TAG,
+                        LogLevel.INFO,
+                        { long1 = DELAY_DURATION.inWholeSeconds },
+                        { "Waiting $long1 seconds before showing the satellite icon" }
+                    )
+                    delay(DELAY_DURATION)
+                    flowOf(true)
                 } else {
-                    combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
-                        isSatelliteAllowed,
-                        isAirplaneMode ->
-                        isSatelliteAllowed && !isAirplaneMode
-                    }
+                    flowOf(false)
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     val icon: StateFlow<Icon?> =
         combine(
-                shouldShowIcon,
+                shouldActuallyShowIcon,
                 interactor.connectionState,
                 interactor.signalStrength,
             ) { shouldShow, state, signalStrength ->
@@ -71,4 +97,9 @@
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    companion object {
+        private const val TAG = "DeviceBasedSatelliteViewModel"
+        private val DELAY_DURATION = 10.seconds
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 7b652c1..6124f63 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -23,11 +23,13 @@
 import android.os.Looper;
 import android.os.Process;
 
+import com.android.systemui.Flags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.BroadcastRunning;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.NotifInflation;
 
 import dagger.Module;
 import dagger.Provides;
@@ -50,6 +52,8 @@
     private static final Long LONG_SLOW_DELIVERY_THRESHOLD = 2500L;
     private static final Long BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L;
     private static final Long BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L;
+    private static final Long NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L;
+    private static final Long NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L;
 
     /** Background Looper */
     @Provides
@@ -90,6 +94,24 @@
         return thread.getLooper();
     }
 
+    /** Notification inflation Looper */
+    @Provides
+    @SysUISingleton
+    @NotifInflation
+    public static Looper provideNotifInflationLooper(@Background Looper bgLooper) {
+        if (!Flags.dedicatedNotifInflationThread()) {
+            return bgLooper;
+        }
+
+        final HandlerThread thread = new HandlerThread("NotifInflation",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+        final Looper looper = thread.getLooper();
+        looper.setSlowLogThresholdMs(NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD,
+                NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD);
+        return looper;
+    }
+
     /**
      * Background Handler.
      *
@@ -225,4 +247,12 @@
         thread.start();
         return new Handler(thread.getLooper());
     }
+
+    /** */
+    @Provides
+    @SysUISingleton
+    @NotifInflation
+    public static Executor provideNotifInflationExecutor(@NotifInflation Looper looper) {
+        return new ExecutorImpl(looper);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt
new file mode 100644
index 0000000..66df45c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.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.volume.dagger
+
+import android.content.Context
+import androidx.slice.SliceViewManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepositoryImpl
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module that provides ANC controlling backend. */
+@Module
+interface AncModule {
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun provideAncSliceRepository(
+            @Application context: Context,
+            implFactory: AncSliceRepositoryImpl.Factory
+        ): AncSliceRepository = implFactory.create(SliceViewManager.getInstance(context))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 3285637..c6aee42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -57,6 +57,7 @@
 @Module(
         includes = {
                 AudioModule.class,
+                AncModule.class,
                 CaptioningModule.class,
                 MediaDevicesModule.class
         },
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
new file mode 100644
index 0000000..8f18aa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.volume.panel.component.anc.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.net.Uri
+import androidx.slice.Slice
+import androidx.slice.SliceViewManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.slice.sliceForUri
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/** Provides ANC slice data */
+interface AncSliceRepository {
+
+    /**
+     * ANC slice with a given width. Emits null when there is no ANC slice available. This can mean
+     * that:
+     * - there is no supported device connected;
+     * - there is no slice provider for the uri;
+     */
+    fun ancSlice(width: Int): Flow<Slice?>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class AncSliceRepositoryImpl
+@AssistedInject
+constructor(
+    mediaRepositoryFactory: LocalMediaRepositoryFactory,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
+    @Assisted private val sliceViewManager: SliceViewManager,
+) : AncSliceRepository {
+
+    private val localMediaRepository = mediaRepositoryFactory.create(null)
+
+    override fun ancSlice(width: Int): Flow<Slice?> {
+        return localMediaRepository.currentConnectedDevice
+            .map { (it as? BluetoothMediaDevice)?.cachedDevice?.device?.getExtraControlUri(width) }
+            .distinctUntilChanged()
+            .flatMapLatest { sliceUri ->
+                sliceUri ?: return@flatMapLatest flowOf(null)
+                sliceViewManager.sliceForUri(sliceUri)
+            }
+            .flowOn(backgroundCoroutineContext)
+    }
+
+    private fun BluetoothDevice.getExtraControlUri(width: Int): Uri? {
+        val uri: String? = BluetoothUtils.getControlUriMetaData(this)
+        uri ?: return null
+
+        return if (uri.isEmpty()) {
+            null
+        } else {
+            Uri.parse(
+                "$uri$width" +
+                    "&version=${SliceParameters.VERSION}" +
+                    "&is_collapsed=${SliceParameters.IS_COLLAPSED}"
+            )
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(sliceViewManager: SliceViewManager): AncSliceRepositoryImpl
+    }
+
+    private object SliceParameters {
+        /**
+         * Slice version
+         * 1) legacy slice
+         * 2) new slice
+         */
+        const val VERSION = 2
+
+        /**
+         * Collapsed slice shows a single button, and expanded shows a row buttons. Supported since
+         * [VERSION]==2.
+         */
+        const val IS_COLLAPSED = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
new file mode 100644
index 0000000..89b9274
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.volume.panel.component.anc.domain
+
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Determines if ANC component is available for the Volume Panel. */
+@VolumePanelScope
+class AncAvailabilityCriteria
+@Inject
+constructor(
+    private val ancSliceInteractor: AncSliceInteractor,
+) : ComponentAvailabilityCriteria {
+
+    override fun isAvailable(): Flow<Boolean> = ancSliceInteractor.ancSlice.map { it != null }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
new file mode 100644
index 0000000..91af622
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.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.volume.panel.component.anc.domain.interactor
+
+import android.app.slice.Slice.HINT_ERROR
+import android.app.slice.SliceItem.FORMAT_SLICE
+import androidx.slice.Slice
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** Provides a valid slice from [AncSliceRepository]. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class AncSliceInteractor
+@Inject
+constructor(
+    private val ancSliceRepository: AncSliceRepository,
+    scope: CoroutineScope,
+) {
+
+    // Start with a positive width to check is the Slice is available.
+    private val width = MutableStateFlow(1)
+
+    /** Provides a valid ANC slice. */
+    val ancSlice: SharedFlow<Slice?> =
+        width
+            .flatMapLatest { width -> ancSliceRepository.ancSlice(width) }
+            .map { slice ->
+                if (slice?.isValidSlice() == true) {
+                    slice
+                } else {
+                    null
+                }
+            }
+            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+    /** Updates the width of the [ancSlice] */
+    fun changeWidth(newWidth: Int) {
+        width.value = newWidth
+    }
+
+    private fun Slice.isValidSlice(): Boolean {
+        if (hints.contains(HINT_ERROR)) {
+            return false
+        }
+        for (item in items) {
+            if (item.format == FORMAT_SLICE) {
+                return true
+            }
+        }
+        return false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 1f52260..d8cd128 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -21,7 +21,7 @@
 import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
+import com.android.systemui.media.controls.util.LocalMediaManagerFactory
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 438f0f4..cc36cfa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -18,8 +18,6 @@
 
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
-import static com.android.systemui.flags.Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -37,9 +35,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -60,13 +56,9 @@
     @Mock
     private NavigationBarController mNavigationBarController;
     @Mock
-    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    @Mock
     private ConnectedDisplayKeyguardPresentation.Factory
             mConnectedDisplayKeyguardPresentationFactory;
     @Mock
-    private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
-    @Mock
     private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
     @Mock
     private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
@@ -77,7 +69,6 @@
     private Executor mBackgroundExecutor = Runnable::run;
     private KeyguardDisplayManager mManager;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-    private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
     // The default and secondary displays are both in the default group
     private Display mDefaultDisplay;
     private Display mSecondaryDisplay;
@@ -88,15 +79,13 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, false);
         mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
-                mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
-                mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController,
-                mConnectedDisplayKeyguardPresentationFactory, mFakeFeatureFlags));
-        doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+                mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper,
+                mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory));
         doReturn(mConnectedDisplayKeyguardPresentation).when(
                 mConnectedDisplayKeyguardPresentationFactory).create(any());
-
+        doReturn(mConnectedDisplayKeyguardPresentation).when(mManager)
+                .createPresentation(any());
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
                 new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
         mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(),
@@ -152,9 +141,8 @@
     }
 
     @Test
-    public void testShow_withClockPresentationFlagEnabled_presentationCreated() {
+    public void testShow_presentationCreated() {
         when(mManager.createPresentation(any())).thenCallRealMethod();
-        mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, true);
         mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
 
         mManager.show();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
deleted file mode 100644
index 5102957..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2019 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.keyguard;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.res.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class KeyguardPresentationTest extends SysuiTestCase {
-
-    @Mock
-    KeyguardClockSwitch mMockKeyguardClockSwitch;
-    @Mock
-    KeyguardSliceView mMockKeyguardSliceView;
-    @Mock
-    KeyguardStatusView mMockKeyguardStatusView;
-    @Mock
-    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    @Mock
-    private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
-    @Mock
-    private KeyguardClockSwitchController mKeyguardClockSwitchController;
-
-    LayoutInflater mLayoutInflater;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext);
-        when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
-        when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
-        when(mMockKeyguardStatusView.findViewById(R.id.clock)).thenReturn(mMockKeyguardStatusView);
-        when(mKeyguardStatusViewComponentFactory.build(any(KeyguardStatusView.class),
-                any(Display.class)))
-                .thenReturn(mKeyguardStatusViewComponent);
-        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
-                .thenReturn(mKeyguardClockSwitchController);
-
-        allowTestableLooperAsMainThread();
-
-        mLayoutInflater = LayoutInflater.from(mContext);
-        mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
-
-            @Override
-            public View onCreateView(View parent, String name, Context context,
-                    AttributeSet attrs) {
-                return onCreateView(name, context, attrs);
-            }
-
-            @Override
-            public View onCreateView(String name, Context context, AttributeSet attrs) {
-                if ("com.android.keyguard.KeyguardStatusView".equals(name)) {
-                    return mMockKeyguardStatusView;
-                } else if ("com.android.keyguard.KeyguardClockSwitch".equals(name)) {
-                    return mMockKeyguardClockSwitch;
-                } else if ("com.android.keyguard.KeyguardSliceView".equals(name)) {
-                    return mMockKeyguardStatusView;
-                }
-                return null;
-            }
-        });
-    }
-
-    @After
-    public void tearDown() {
-        disallowTestableLooperAsMainThread();
-    }
-
-    @Test
-    public void testInflation_doesntCrash() {
-        final Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
-                Display.DEFAULT_DISPLAY);
-        KeyguardPresentation keyguardPresentation = new KeyguardPresentation(mContext, display,
-                mKeyguardStatusViewComponentFactory);
-        keyguardPresentation.onCreate(null /*savedInstanceState */);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index a19a0c7..d2a17c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -89,7 +89,7 @@
         loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
 
     private fun CameraProtectionInfo.toTestableVersion() =
-        TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
+        TestableProtectionInfo(logicalCameraId, physicalCameraId, bounds, displayUniqueId)
 
     /**
      * "Testable" version, because the original version contains a Path property, which doesn't
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
index f769b4e..6cb77cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.graphics.Rect
 import com.android.systemui.res.R
 
 class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
@@ -36,7 +37,10 @@
         addInnerCameraProtection()
     }
 
-    fun addOuterCameraProtection(displayUniqueId: String = "111") {
+    fun addOuterCameraProtection(
+        displayUniqueId: String = "111",
+        bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+    ) {
         context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedPhysicalCameraId,
@@ -44,7 +48,7 @@
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_frontBuiltInDisplayCutoutProtection,
-            "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+            bounds.asPath(),
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedScreenUniqueId,
@@ -52,7 +56,10 @@
         )
     }
 
-    fun addInnerCameraProtection(displayUniqueId: String = "222") {
+    fun addInnerCameraProtection(
+        displayUniqueId: String = "222",
+        bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+    ) {
         context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedInnerPhysicalCameraId,
@@ -60,11 +67,13 @@
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_innerBuiltInDisplayCutoutProtection,
-            "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+            bounds.asPath(),
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedInnerScreenUniqueId,
             displayUniqueId
         )
     }
+
+    private fun Rect.asPath() = "M $left, $top H $right V $bottom H $left Z"
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
index f37c4ae..61c7e1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui
 
+import android.graphics.Rect
 import android.view.Display
 import android.view.DisplayAdjustments
 import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -39,7 +44,7 @@
         val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
         val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()
 
         assertThat(sysUICutout).isNull()
     }
@@ -50,7 +55,7 @@
         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
         val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
     }
@@ -61,7 +66,7 @@
         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
         val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -72,7 +77,7 @@
         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
         val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNotNull()
     }
@@ -83,7 +88,7 @@
         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
         val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -94,7 +99,7 @@
         val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
         val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -105,20 +110,170 @@
         val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
         val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
 
+    @Test
+    fun cutoutInfo_rotation0_returnsOriginalProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_0,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(
+                Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+    }
+
+    @Test
+    fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_90,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(Rect(/* left = */ 10, /* top = */ 10, /* right = */ 110, /* bottom = */ 60))
+    }
+
+    @Test
+    fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() {
+        val displayNaturalWidth = 500
+        val displayNaturalHeight = 1000
+        val originalProtectionBounds =
+            Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+        // Safe copy as we don't know at which layer the mutation could happen
+        val originalProtectionBoundsCopy = Rect(originalProtectionBounds)
+        val display =
+            createDisplay(
+                uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+                rotation = Surface.ROTATION_180,
+                width = displayNaturalWidth,
+                height = displayNaturalHeight,
+            )
+        fakeProtectionLoader.addOuterCameraProtection(
+            displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+            bounds = originalProtectionBounds
+        )
+        val provider =
+            SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+
+        // Here we get the rotated bounds once
+        provider.cutoutInfoForCurrentDisplayAndRotation()
+
+        // Rotate display back to original rotation
+        whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+
+        // Now the bounds should match the original ones. We are checking for mutation since Rect
+        // is mutable and has many methods that mutate the instance, and it is easy to do it by
+        // mistake.
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+        assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy)
+    }
+
+    @Test
+    fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_180,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(Rect(/* left = */ 10, /* top = */ 890, /* right = */ 60, /* bottom = */ 990))
+    }
+
+    @Test
+    fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_270,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(
+                Rect(/* left = */ 890, /* top = */ 440, /* right = */ 990, /* bottom = */ 490)
+            )
+    }
+
+    private fun setUpProviderWithCameraProtection(
+        displayWidth: Int,
+        displayHeight: Int,
+        rotation: Int = Surface.ROTATION_0,
+        protectionBounds: Rect,
+    ): SysUICutoutProvider {
+        val display =
+            createDisplay(
+                uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+                rotation = rotation,
+                width =
+                    if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+                        displayWidth
+                    } else {
+                        displayHeight
+                    },
+                height =
+                    if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
+                        displayHeight
+                    else displayWidth,
+            )
+        fakeProtectionLoader.addOuterCameraProtection(
+            displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+            bounds = protectionBounds
+        )
+        return SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+    }
+
     companion object {
         private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
         private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
 
         private fun createDisplay(
+            width: Int = 500,
+            height: Int = 1000,
+            @Rotation rotation: Int = Surface.ROTATION_0,
             uniqueId: String? = "uniqueId",
             cutout: DisplayCutout? = mock<DisplayCutout>()
         ) =
             mock<Display> {
+                whenever(this.getDisplayInfo(any())).thenAnswer {
+                    val displayInfo = it.arguments[0] as DisplayInfo
+                    displayInfo.rotation = rotation
+                    displayInfo.logicalWidth = width
+                    displayInfo.logicalHeight = height
+                    return@thenAnswer true
+                }
+                // Setting width and height to smaller values to simulate real behavior of this API
+                // not always returning the real display size
+                whenever(this.width).thenReturn(width - 5)
+                whenever(this.height).thenReturn(height - 10)
+                whenever(this.rotation).thenReturn(rotation)
                 whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
                 whenever(this.cutout).thenReturn(cutout)
                 whenever(this.uniqueId).thenReturn(uniqueId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index f770a38..6e00b70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -21,6 +21,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyDouble;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
@@ -40,6 +42,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -71,6 +75,10 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private AccessibilityManager mAccessibilityManager;
+    @Captor
+    private ArgumentCaptor<FalsingDataProvider.SessionListener> mSessionListenerArgumentCaptor;
+    @Captor
+    private ArgumentCaptor<HistoryTracker.BeliefListener> mBeliefListenerArgumentCaptor;
 
     private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
     private final FalsingClassifier.Result mFalsedResult =
@@ -194,4 +202,28 @@
         when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
     }
+
+    @Test
+    public void testAddAndRemoveFalsingBeliefListener() {
+        verify(mHistoryTracker, never()).addBeliefListener(any());
+
+        // Session started
+        final FalsingDataProvider.SessionListener sessionListener = captureSessionListener();
+        sessionListener.onSessionStarted();
+
+        // Verify belief listener added when session started
+        verify(mHistoryTracker).addBeliefListener(mBeliefListenerArgumentCaptor.capture());
+        verify(mHistoryTracker, never()).removeBeliefListener(any());
+
+        // Session ended
+        sessionListener.onSessionEnded();
+
+        // Verify belief listener removed when session ended
+        verify(mHistoryTracker).removeBeliefListener(mBeliefListenerArgumentCaptor.getValue());
+    }
+
+    private FalsingDataProvider.SessionListener captureSessionListener() {
+        verify(mFalsingDataProvider).addSessionListener(mSessionListenerArgumentCaptor.capture());
+        return mSessionListenerArgumentCaptor.getValue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index fcb18f5..3f13033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -33,6 +33,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -85,6 +86,8 @@
     private BatteryController mBatteryController;
     @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
+    private CommunalInteractor mCommunalInteractor;
     private final DockManagerFake mDockManager = new DockManagerFake();
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -102,7 +105,8 @@
                 mStatusBarStateController, mKeyguardStateController,
                 () -> mShadeInteractor, mBatteryController,
                 mDockManager, mFakeExecutor,
-                mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor
+                mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor,
+                () -> mCommunalInteractor
         );
         mFalsingCollector.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 1851582..c65a117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -27,16 +27,20 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -59,6 +63,8 @@
     @Mock
     private ClipboardManager mClipboardManager;
     @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
     private ClipboardToast mClipboardToast;
@@ -96,7 +102,7 @@
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
         mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
-                mClipboardToast, mClipboardManager, mUiEventLogger);
+                mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
     }
 
 
@@ -191,6 +197,34 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
+    public void test_deviceLocked_showsToast() {
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+        verify(mClipboardToast, times(1)).showCopiedToast();
+        verifyZeroInteractions(mOverlayControllerProvider);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
+    public void test_deviceLocked_legacyBehavior_showsInteractiveUI() {
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
+        verifyZeroInteractions(mClipboardToast);
+    }
+
+    @Test
     public void test_nullClipData_showsNothing() {
         when(mClipboardManager.getPrimaryClip()).thenReturn(null);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
index 2bf9ab2..05b4a41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
@@ -37,7 +37,7 @@
 import com.android.systemui.complication.dagger.DreamMediaEntryComplicationComponent;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.ui.MediaCarouselController;
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController;
 import com.android.systemui.media.dream.MediaDreamComplication;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index cf8fe79..2b51863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -2,6 +2,9 @@
 
 import android.content.ComponentCallbacks2
 import android.graphics.HardwareRenderer
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -23,6 +26,7 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -48,10 +52,11 @@
     @Mock private lateinit var globalWindowManager: GlobalWindowManager
     private lateinit var resourceTrimmer: ResourceTrimmer
 
+    @Rule @JvmField public val setFlagsRule = SetFlagsRule()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
         featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
         keyguardRepository.setDozeAmount(0f)
         keyguardRepository.setKeyguardGoingAway(false)
@@ -76,6 +81,7 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun noChange_noOutputChanges() =
         testScope.runTest {
             testScope.runCurrent()
@@ -83,6 +89,7 @@
         }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun dozeAodDisabled_sleep_trimsMemory() =
         testScope.runTest {
             powerInteractor.setAsleepForTest()
@@ -93,6 +100,27 @@
         }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+    fun dozeAodDisabled_flagDisabled_sleep_doesntTrimMemory() =
+        testScope.runTest {
+            powerInteractor.setAsleepForTest()
+            testScope.runCurrent()
+            verifyZeroInteractions(globalWindowManager)
+        }
+
+    @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+    fun dozeEnabled_flagDisabled_sleepWithFullDozeAmount_doesntTrimMemory() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(true)
+            keyguardRepository.setDozeAmount(1f)
+            powerInteractor.setAsleepForTest()
+            testScope.runCurrent()
+            verifyZeroInteractions(globalWindowManager)
+        }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
         testScope.runTest {
             keyguardRepository.setDreaming(true)
@@ -105,6 +133,7 @@
         }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
         testScope.runTest {
             keyguardRepository.setDreaming(true)
@@ -115,6 +144,7 @@
         }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
         testScope.runTest {
             keyguardRepository.setDreaming(true)
@@ -141,6 +171,7 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
         testScope.runTest {
             keyguardRepository.setDreaming(true)
@@ -172,6 +203,7 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun keyguardTransitionsToGone_trimsFontCache() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionSteps(
@@ -186,6 +218,7 @@
         }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
     fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
         testScope.runTest {
             featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
index 3437365..4e976d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.media.controls
 
 import com.android.internal.logging.InstanceId
-import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.shared.model.MediaData
 
 class MediaTestUtils {
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index fb101dd..bb5b572 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline;
+package com.android.systemui.media.controls.domain.pipeline;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -33,8 +33,8 @@
 
 import com.android.internal.logging.InstanceId;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.player.MediaDeviceData;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.MediaDeviceData;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterTest.kt
index 94b9fa4..59eb7bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.app.smartspace.SmartspaceAction
 import android.os.Bundle
@@ -25,10 +25,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaPlayerData
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerTest.kt
index 59d8104..7d5305b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.app.IUriGrantsManager
 import android.app.Notification
@@ -51,13 +51,13 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.resume.MediaResumeListener
-import com.android.systemui.media.controls.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index e3c4c28..14fe182 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.bluetooth.BluetoothLeBroadcast
 import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -39,8 +39,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.LocalMediaManagerFactory
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 3099609..5a3c220 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.media.session.MediaController
 import android.media.session.MediaController.PlaybackInfo
@@ -25,7 +25,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 8baa06a..3cc65c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
 
 import android.media.MediaMetadata
 import android.media.session.MediaController
@@ -24,8 +24,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
index 530b86e..55ff231 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
 
 import android.app.PendingIntent
 import android.content.ComponentName
@@ -34,10 +34,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
index b45e66b..8dfa5b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
 
 import android.content.ComponentName
 import android.content.Context
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
index f7c20ac..473dc47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.shared
 
 import android.app.smartspace.SmartspaceAction
 import android.graphics.drawable.Icon
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
index 32b822d..b509e77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
@@ -20,7 +20,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.ui.controller.MediaControlPanel
+import com.android.systemui.media.controls.ui.controller.MediaPlayerData
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index 99f56b1..eb885fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.Drawable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
index a943746..aa297b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import android.animation.ValueAnimator
 import android.graphics.Color
@@ -22,8 +22,8 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.surfaceeffects.ripple.MultiRippleController
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index 323b781..711669e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
 
 import android.animation.Animator
 import android.test.suitebuilder.annotation.SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 4ec29ce..8a6b272 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.binder
 
 import android.animation.Animator
 import android.animation.ObjectAnimator
@@ -25,7 +25,9 @@
 import android.widget.TextView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 50f0eb4..9f5260c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.provider.Settings
 import android.test.suitebuilder.annotation.SmallTest
@@ -25,6 +25,8 @@
 import android.widget.FrameLayout
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index f3b9102..f755199 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.app.PendingIntent
 import android.content.res.ColorStateList
@@ -38,11 +38,13 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaScrollView
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 93d6121..2e7829d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.animation.Animator
 import android.animation.AnimatorSet
@@ -69,19 +69,19 @@
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.models.player.MediaAction
-import com.android.systemui.media.controls.models.player.MediaButton
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.player.SeekBarObserver
-import com.android.systemui.media.controls.models.player.SeekBarViewModel
-import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.KEY_SMARTSPACE_APP_NAME
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 87d093f..85291b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.graphics.Rect
 import android.provider.Settings
@@ -31,7 +31,10 @@
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index b701d7f..a73bb2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import android.content.res.Configuration
 import android.content.res.Configuration.ORIENTATION_LANDSCAPE
@@ -24,8 +24,9 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
 import com.android.systemui.util.animation.MeasurementInput
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
index d6cff81..0319aaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
 
 import android.graphics.Canvas
 import android.graphics.Color
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index 74b3fce..1208369 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
index c829d4c..d3c703c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.view
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
index e3c8b05..e1c2d3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.viewmodel
 
 import android.media.MediaMetadata
 import android.media.session.MediaController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index f7873aa..ca403e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -47,6 +48,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -126,6 +128,13 @@
                 mNotifCollection, mDialogTransitionAnimator,
                 mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
+
+        // Using a fake package will cause routing operations to fail, so we intercept
+        // scanning-related operations.
+        mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
+        doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
+        doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
index ce885c0..a828843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -25,7 +25,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.util.animation.UniqueObjectHostView;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 8a31664..ff7c970 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -33,8 +33,8 @@
 import com.android.systemui.complication.DreamMediaEntryComplication;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaData;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index e2be4cb..27f59d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -102,7 +102,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
-        whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
 
         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
         whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 563a3fe..e4a4836 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -57,7 +58,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSComponent;
 import com.android.systemui.qs.external.TileServiceRequestController;
@@ -511,6 +512,28 @@
         );
     }
 
+    @Test
+    public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
+        when(mSceneContainerFlags.isEnabled()).thenReturn(true);
+
+        mUnderTest.onStateChanged(KEYGUARD);
+        assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
+
+        mUnderTest.onStateChanged(SHADE_LOCKED);
+        assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
+    }
+
+    @Test
+    public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
+        when(mSceneContainerFlags.isEnabled()).thenReturn(true);
+
+        mUnderTest.onStateChanged(KEYGUARD);
+        assertThat(mUnderTest.isKeyguardState()).isFalse();
+
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+        assertThat(mUnderTest.isKeyguardState()).isFalse();
+    }
+
     private QSImpl instantiate() {
         setupQsComponent();
         setUpViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index da8d29c..65ede89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -45,7 +45,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.logging.QSLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 1f7a029..85d7d98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -10,8 +10,8 @@
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.customize.QSCustomizerController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2db79c2..2c14308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index db455cb..23c3334 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -115,9 +115,9 @@
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 061f88e..42342d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -54,8 +54,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 0933425..91701b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -15,7 +15,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 4b145d8..5c45b2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,7 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index f2ef4e1..a4f88fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -63,7 +63,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 2b3f9d0..6fec9ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,7 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index f53fc46..cd0652e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -28,7 +29,10 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.mockito.MockitoAnnotations
@@ -61,6 +65,7 @@
                 interactor,
                 testScope.backgroundScope,
                 airplaneModeRepository,
+                FakeLogBuffer.Factory.create(),
             )
     }
 
@@ -121,8 +126,9 @@
             assertThat(latest).isNull()
         }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun icon_satelliteIsOff() =
+    fun icon_satelliteIsOn() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -133,7 +139,45 @@
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = false
 
-            // THEN icon is null because we have service
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // Wait for delay to be completed
+            advanceTimeBy(10.seconds)
+
+            // THEN icon is set because we don't have service
             assertThat(latest).isInstanceOf(Icon::class.java)
         }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun icon_hysteresisWhenEnablingIcon() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null because of the hysteresis
+            assertThat(latest).isNull()
+
+            // Wait for delay to be completed
+            advanceTimeBy(10.seconds)
+
+            // THEN icon is set after the delay
+            assertThat(latest).isInstanceOf(Icon::class.java)
+
+            // GIVEN apm is enabled
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            // THEN icon is null immediately
+            assertThat(latest).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index b8c880b..62a1aa9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import com.android.systemui.log.dagger.SceneFrameworkLog
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.model.SysUiState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.DarkIconDispatcher
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index 43897c9..cceb3ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -17,9 +17,10 @@
 package com.android.systemui.flags
 
 import android.platform.test.annotations.EnableFlags
+import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 
 /**
@@ -30,6 +31,7 @@
     FLAG_SCENE_CONTAINER,
     FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
     FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+    FLAG_COMPOSE_LOCKSCREEN,
     FLAG_MEDIA_IN_SCENE_CONTAINER,
 )
 @Retention(AnnotationRetention.RUNTIME)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index 733340c..460913f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -22,11 +22,13 @@
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
     AodToLockscreenTransitionViewModel(
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+        shadeInteractor = shadeInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 4939237b..ecf66a2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
 import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -56,5 +57,6 @@
         screenOffAnimationController = screenOffAnimationController,
         aodBurnInViewModel = aodBurnInViewModel,
         aodAlphaViewModel = aodAlphaViewModel,
+        shadeInteractor = shadeInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
similarity index 93%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
index db2cdfa..7c24b4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt
index db2cdfa..960a069 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.privacy
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+var Kosmos.privacyDialogController: PrivacyDialogController by
+    Kosmos.Fixture { mock<PrivacyDialogController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt
index db2cdfa..7628c0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.privacy
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+var Kosmos.privacyDialogControllerV2: PrivacyDialogControllerV2 by
+    Kosmos.Fixture { mock<PrivacyDialogControllerV2>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
index db2cdfa..ced29cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.qs.pipeline.data.repository
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.qs.pipeline.shared.TileSpec
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+class FakeDefaultTilesRepository(override val defaultTiles: List<TileSpec> = emptyList()) :
+    DefaultTilesRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index ae4cf3a..a9cce69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -23,7 +23,9 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-class FakeTileSpecRepository : TileSpecRepository {
+class FakeTileSpecRepository(
+    private val defaultTilesRepository: DefaultTilesRepository = FakeDefaultTilesRepository()
+) : TileSpecRepository {
 
     private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
 
@@ -67,4 +69,8 @@
             value = UserTileSpecRepository.reconcileTiles(value, currentAutoAdded, restoreData)
         }
     }
+
+    override suspend fun prependDefault(userId: Int) {
+        with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles + value }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 0091482..604c16f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -18,7 +18,17 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() }
+/** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */
+var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) }
+val Kosmos.minimumTilesRepository: MinimumTilesRepository by
+    Kosmos.Fixture { fakeMinimumTilesRepository }
+
+var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
+val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+    Kosmos.Fixture { fakeDefaultTilesRepository }
+
+val Kosmos.fakeTileSpecRepository by
+    Kosmos.Fixture { FakeTileSpecRepository(defaultTilesRepository) }
 var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository }
 
 val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index 67df563..9ef44c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.qs.newQSTileFactory
 import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
 import com.android.systemui.qs.pipeline.shared.logging.qsLogger
 import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
@@ -37,6 +38,7 @@
             tileSpecRepository,
             installedTilesRepository,
             userRepository,
+            minimumTilesRepository,
             customTileStatePersister,
             { newQSTileFactory },
             qsTileFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt
new file mode 100644
index 0000000..5bc61e2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.shade.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.privacy.PrivacyItem
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [PrivacyChipRepository] */
+@SysUISingleton
+class FakePrivacyChipRepository @Inject constructor() : PrivacyChipRepository {
+    private val _isSafetyCenterEnabled = MutableStateFlow(false)
+    override val isSafetyCenterEnabled = _isSafetyCenterEnabled
+
+    private val _privacyItems: MutableStateFlow<List<PrivacyItem>> = MutableStateFlow(emptyList())
+    override val privacyItems = _privacyItems
+
+    private val _isMicCameraIndicationEnabled = MutableStateFlow(false)
+    override val isMicCameraIndicationEnabled = _isMicCameraIndicationEnabled
+
+    private val _isLocationIndicationEnabled = MutableStateFlow(false)
+    override val isLocationIndicationEnabled = _isLocationIndicationEnabled
+
+    fun setIsSafetyCenterEnabled(value: Boolean) {
+        _isSafetyCenterEnabled.value = value
+    }
+
+    fun setPrivacyItems(value: List<PrivacyItem>) {
+        _privacyItems.value = value
+    }
+
+    fun setIsMicCameraIndicationEnabled(value: Boolean) {
+        _isMicCameraIndicationEnabled.value = value
+    }
+
+    fun setIsLocationIndicationEnabled(value: Boolean) {
+        _isLocationIndicationEnabled.value = value
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryKosmos.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryKosmos.kt
index db2cdfa..2428c61 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryKosmos.kt
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.ui
+package com.android.systemui.shade.data.repository
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+var Kosmos.privacyChipRepository: PrivacyChipRepository by
+    Kosmos.Fixture { fakePrivacyChipRepository }
+val Kosmos.fakePrivacyChipRepository: FakePrivacyChipRepository by
+    Kosmos.Fixture { FakePrivacyChipRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt
new file mode 100644
index 0000000..7334286
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.privacy.privacyDialogController
+import com.android.systemui.privacy.privacyDialogControllerV2
+import com.android.systemui.shade.data.repository.fakePrivacyChipRepository
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+
+var Kosmos.privacyChipInteractor: PrivacyChipInteractor by
+    Kosmos.Fixture {
+        PrivacyChipInteractor(
+            applicationScope = applicationCoroutineScope,
+            repository = fakePrivacyChipRepository,
+            privacyDialogController = privacyDialogController,
+            privacyDialogControllerV2 = privacyDialogControllerV2,
+            deviceProvisionedController = deviceProvisionedController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index 81888c484..e5072f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.wakefulnessLifecycle
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.media.controls.ui.mediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaHierarchyManager
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt
new file mode 100644
index 0000000..fc406ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.volume.panel.component.anc
+
+import androidx.slice.Slice
+import androidx.slice.SliceItem
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+object FakeSliceFactory {
+
+    fun createSlice(hasError: Boolean, hasSliceItem: Boolean): Slice {
+        return mock {
+            val sliceItem: SliceItem = mock {
+                whenever(format).thenReturn(android.app.slice.SliceItem.FORMAT_SLICE)
+            }
+
+            whenever(items)
+                .thenReturn(
+                    buildList {
+                        if (hasSliceItem) {
+                            add(sliceItem)
+                        }
+                    }
+                )
+
+            whenever(hints)
+                .thenReturn(
+                    buildList {
+                        if (hasError) {
+                            add(android.app.slice.Slice.HINT_ERROR)
+                        }
+                    }
+                )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
new file mode 100644
index 0000000..f9b7e69
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.volume.panel.component.anc
+
+import androidx.slice.SliceViewManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.panel.component.anc.data.repository.FakeAncSliceRepository
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+
+var Kosmos.sliceViewManager: SliceViewManager by Kosmos.Fixture { mock {} }
+val Kosmos.ancSliceRepository by Kosmos.Fixture { FakeAncSliceRepository() }
+val Kosmos.ancSliceInteractor by
+    Kosmos.Fixture { AncSliceInteractor(ancSliceRepository, testScope.backgroundScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
new file mode 100644
index 0000000..b66d7f9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.volume.panel.component.anc.data.repository
+
+import androidx.slice.Slice
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAncSliceRepository : AncSliceRepository {
+
+    private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>()
+
+    override fun ancSlice(width: Int): Flow<Slice?> =
+        sliceByWidth.getOrPut(width) { MutableStateFlow(null) }
+
+    fun putSlice(width: Int, slice: Slice?) {
+        sliceByWidth.getOrPut(width) { MutableStateFlow(null) }.value = slice
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3442a6a..46db624 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -64,6 +64,7 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -112,6 +113,7 @@
 import android.text.TextUtils.SimpleStringSplitter;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -4394,13 +4396,29 @@
             // permittedServices null means all accessibility services are allowed.
             boolean allowed = permittedServices == null || permittedServices.contains(packageName);
             if (allowed) {
-                final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
-                final int mode = appOps.noteOpNoThrow(
-                        AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
-                        uid, packageName, /* attributionTag= */ null, /* message= */ null);
-                final boolean ecmEnabled = mContext.getResources().getBoolean(
-                        R.bool.config_enhancedConfirmationModeEnabled);
-                return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+                if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                        && android.security.Flags.extendEcmToAllSettings()) {
+                    try {
+                        return !mContext.getSystemService(EnhancedConfirmationManager.class)
+                                .isRestricted(packageName,
+                                        AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
+                        return false;
+                    }
+                } else {
+                    try {
+                        final int mode = mContext.getSystemService(AppOpsManager.class)
+                                .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                                        uid, packageName);
+                        final boolean ecmEnabled = mContext.getResources().getBoolean(
+                                com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+                        return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+                    } catch (Exception e) {
+                        // Fallback in case if app ops is not available in testing.
+                        return false;
+                    }
+                }
             }
             return false;
         } finally {
@@ -4423,8 +4441,21 @@
             return true;
         }
 
-        RestrictedLockUtils.sendShowRestrictedSettingDialogIntent(mContext,
-                packageName, uid);
+        if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                && android.security.Flags.extendEcmToAllSettings()) {
+            try {
+                Intent settingDialogIntent = mContext
+                        .getSystemService(EnhancedConfirmationManager.class)
+                        .createRestrictedSettingDialogIntent(packageName,
+                                AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+                mContext.startActivity(settingDialogIntent);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
+            }
+        } else {
+            RestrictedLockUtils.sendShowRestrictedSettingDialogIntent(mContext,
+                    packageName, uid);
+        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 507fd9e..595d16d 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -34,7 +34,6 @@
     static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdStatsReporter" : TAG_AM;
 
     public static final int KILL_OCCURRED_MSG_SIZE = 80;
-    public static final int STATE_CHANGED_MSG_SIZE = 8;
 
     private static final int PRESSURE_AFTER_KILL = 0;
     private static final int NOT_RESPONDING = 1;
@@ -79,16 +78,6 @@
         }
     }
 
-    /**
-     * Processes the LMK_STATE_CHANGED packet
-     * Logs the change in LMKD state which is used as start/stop boundaries for logging
-     * LMK_KILL_OCCURRED event.
-     * Code: LMK_STATE_CHANGED = 54
-     */
-    public static void logStateChanged(int state) {
-        FrameworkStatsLog.write(FrameworkStatsLog.LMK_STATE_CHANGED, state);
-    }
-
     private static int mapKillReason(int reason) {
         switch (reason) {
             case PRESSURE_AFTER_KILL:
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3adea7a..89c8994 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -348,7 +348,7 @@
     // LMK_PROCKILL
     // LMK_UPDATE_PROPS
     // LMK_KILL_OCCURRED
-    // LMK_STATE_CHANGED
+    // LMK_START_MONITORING
     static final byte LMK_TARGET = 0;
     static final byte LMK_PROCPRIO = 1;
     static final byte LMK_PROCREMOVE = 2;
@@ -358,7 +358,6 @@
     static final byte LMK_PROCKILL = 6; // Note: this is an unsolicited command
     static final byte LMK_UPDATE_PROPS = 7;
     static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event
-    static final byte LMK_STATE_CHANGED = 9; // Msg to subscribed clients on state changed
     static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier
 
     // Low Memory Killer Daemon command codes.
@@ -965,14 +964,6 @@
                                                 foregroundServices.first,
                                                 foregroundServices.second);
                                         return true;
-                                    case LMK_STATE_CHANGED:
-                                        if (receivedLen
-                                                != LmkdStatsReporter.STATE_CHANGED_MSG_SIZE) {
-                                            return false;
-                                        }
-                                        final int state = inputData.readInt();
-                                        LmkdStatsReporter.logStateChanged(state);
-                                        return true;
                                     default:
                                         return false;
                                 }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index df5a824..767f54d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -161,6 +161,7 @@
         "media_solutions",
         "nfc",
         "pdf_viewer",
+        "perfetto",
         "pixel_audio_android",
         "pixel_biometrics_face",
         "pixel_bluetooth",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 102a960..e0c2425 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -167,8 +167,7 @@
                 ads = findBtDeviceStateForAddress(peerAddress, deviceType);
             }
             if (ads != null) {
-                if (ads.getAudioDeviceCategory() != category
-                        && category != AUDIO_DEVICE_CATEGORY_UNKNOWN) {
+                if (ads.getAudioDeviceCategory() != category) {
                     ads.setAudioDeviceCategory(category);
                     mDeviceBroker.postUpdatedAdiDeviceState(ads);
                     mDeviceBroker.postPersistAudioDeviceSettings();
@@ -892,7 +891,7 @@
             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "A2dp config change ignored (scheduled connection change)")
-                        .printLog(TAG));
+                        .printSlog(EventLogger.Event.ALOGI, TAG));
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
                         .record();
                 return;
@@ -929,7 +928,7 @@
                                     "APM handleDeviceConfigChange failed for A2DP device addr="
                                             + address + " codec="
                                             + AudioSystem.audioFormatToString(codec))
-                                    .printLog(TAG));
+                                    .printSlog(EventLogger.Event.ALOGE, TAG));
 
                             // force A2DP device disconnection in case of error so that AudioService
                             // state is consistent with audio policy manager state
@@ -940,7 +939,7 @@
                                     "APM handleDeviceConfigChange success for A2DP device addr="
                                             + address
                                             + " codec=" + AudioSystem.audioFormatToString(codec))
-                                    .printLog(TAG));
+                                    .printSlog(EventLogger.Event.ALOGI, TAG));
                         }
                     }
                 }
@@ -1707,10 +1706,13 @@
                 if (res != AudioSystem.AUDIO_STATUS_OK) {
                     final String reason = "not connecting device 0x" + Integer.toHexString(device)
                             + " due to command error " + res;
-                    Slog.e(TAG, reason);
                     mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
                             .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED)
                             .record();
+                    AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                            "APM failed to make available device 0x" + Integer.toHexString(device)
+                            + "addr=" + address + " error=" + res)
+                            .printSlog(EventLogger.Event.ALOGE, TAG));
                     return false;
                 }
                 mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
@@ -1736,7 +1738,8 @@
                     AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                             "SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
                             + " device addr=" + address
-                            + (connect ? " now available" : " made unavailable")).printLog(TAG));
+                            + (connect ? " now available" : " made unavailable"))
+                            .printSlog(EventLogger.Event.ALOGI, TAG));
                 }
                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
             } else {
@@ -1992,14 +1995,15 @@
         // double connection is made.
         if (res != AudioSystem.AUDIO_STATUS_OK) {
             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                    "APM failed to make available A2DP device addr=" + address
-                            + " error=" + res).printLog(TAG));
+                    "APM failed to make available A2DP device addr="
+                            + Utils.anonymizeBluetoothAddress(address)
+                            + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
             // TODO: connection failed, stop here
             // TODO: return;
         } else {
             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                     "A2DP sink device addr=" + Utils.anonymizeBluetoothAddress(address)
-                            + " now available").printLog(TAG));
+                            + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
         }
 
         // Reset A2DP suspend state each time a new sink is connected
@@ -2240,7 +2244,8 @@
             // removing A2DP device not currently used by AudioPolicy, log but don't act on it
             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                     "A2DP device " + Utils.anonymizeBluetoothAddress(address)
-                            + " made unavailable, was not used")).printLog(TAG));
+                            + " made unavailable, was not used"))
+                    .printSlog(EventLogger.Event.ALOGI, TAG));
             mmi.set(MediaMetrics.Property.EARLY_RETURN,
                     "A2DP device made unavailable, was not used")
                     .record();
@@ -2258,13 +2263,13 @@
             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                     "APM failed to make unavailable A2DP device addr="
                             + Utils.anonymizeBluetoothAddress(address)
-                            + " error=" + res).printLog(TAG));
+                            + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
             // TODO:  failed to disconnect, stop here
             // TODO: return;
         } else {
             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                     "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
-                            + " made unavailable")).printLog(TAG));
+                            + " made unavailable")).printSlog(EventLogger.Event.ALOGI, TAG));
         }
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
 
@@ -2297,10 +2302,22 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpSrcAvailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
                 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
                 AudioSystem.DEVICE_STATE_AVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
+        if (res != AudioSystem.AUDIO_STATUS_OK) {
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                    "APM failed to make available A2DP source device addr="
+                            + Utils.anonymizeBluetoothAddress(address)
+                            + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
+            // TODO: connection failed, stop here
+            // TODO: return
+        } else {
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                    "A2DP source device addr=" + Utils.anonymizeBluetoothAddress(address)
+                            + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
+        }
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
                 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
@@ -2453,14 +2470,14 @@
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM failed to make available LE Audio device addr=" + address
-                                + " error=" + res).printLog(TAG));
+                                + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
                 // TODO: connection failed, stop here
                 // TODO: return;
             } else {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
                                 + " device addr=" + Utils.anonymizeBluetoothAddress(address)
-                                + " now available").printLog(TAG));
+                                + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
             }
             // Reset LEA suspend state each time a new sink is connected
             mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
@@ -2502,13 +2519,13 @@
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM failed to make unavailable LE Audio device addr=" + address
-                                + " error=" + res).printLog(TAG));
+                                + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
                 // TODO:  failed to disconnect, stop here
                 // TODO: return;
             } else {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
-                                + " made unavailable").printLog(TAG));
+                                + " made unavailable").printSlog(EventLogger.Event.ALOGI, TAG));
             }
             mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
         }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 3a63330..88c24e0 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -842,7 +842,8 @@
                         // We must tell sidekick/displayoffload to stop controlling the display
                         // before we can change its power mode, so do that first.
                         if (isDisplayOffloadEnabled) {
-                            if (displayOffloadSession != null) {
+                            if (displayOffloadSession != null
+                                    && !DisplayOffloadSession.isSupportedOffloadState(state)) {
                                 displayOffloadSession.stopOffload();
                             }
                         } else {
@@ -874,8 +875,8 @@
                         // have a sidekick/displayoffload available, tell it now that it can take
                         // control.
                         if (isDisplayOffloadEnabled) {
-                            if (DisplayOffloadSession.isSupportedOffloadState(state)
-                                    && displayOffloadSession != null) {
+                            if (displayOffloadSession != null
+                                    && DisplayOffloadSession.isSupportedOffloadState(state)) {
                                 displayOffloadSession.startOffload();
                             }
                         } else {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 34bb219..b3df784 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -224,10 +224,6 @@
                 mIsConnected = true;
                 service = mService;
             }
-
-            // TODO (b/318745416): Add support for FGS in MediaSession2. Passing a
-            // null playback state means the owning process will not be allowed to
-            // run in the foreground.
             service.onSessionActiveStateChanged(MediaSession2Record.this,
                     /* playbackState= */ null);
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index e7e8096..757b26c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -312,8 +312,7 @@
                 }
                 user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-            boolean isUserEngaged =
-                    record.isActive() && (playbackState == null || playbackState.isActive());
+            boolean isUserEngaged = isUserEngaged(record, playbackState);
 
             Log.d(TAG, "onSessionActiveStateChanged: "
                     + "record=" + record
@@ -325,6 +324,15 @@
         }
     }
 
+    private boolean isUserEngaged(MediaSessionRecordImpl record,
+            @Nullable PlaybackState playbackState) {
+        if (playbackState == null) {
+            // MediaSession2 case
+            return record.checkPlaybackActiveState(/* expected= */ true);
+        }
+        return playbackState.isActive() && record.isActive();
+    }
+
     // Currently only media1 can become global priority session.
     void setGlobalPrioritySession(MediaSessionRecord record) {
         synchronized (mLock) {
@@ -417,16 +425,13 @@
                 return;
             }
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
-            if (playbackState != null) {
-                boolean isUserEngaged = playbackState.isActive() && record.isActive();
-                Log.d(TAG, "onSessionPlaybackStateChanged: "
-                        + "record=" + record
-                        + "playbackState=" + playbackState
-                        + "allowRunningInForeground=" + isUserEngaged);
-                setForegroundServiceAllowance(
-                        record, /* allowRunningInForeground= */ isUserEngaged);
-                reportMediaInteractionEvent(record, isUserEngaged);
-            }
+            boolean isUserEngaged = isUserEngaged(record, playbackState);
+            Log.d(TAG, "onSessionPlaybackStateChanged: "
+                    + "record=" + record
+                    + "playbackState=" + playbackState
+                    + "allowRunningInForeground=" + isUserEngaged);
+            setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+            reportMediaInteractionEvent(record, isUserEngaged);
         }
     }
 
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 59d3d17..5ad5507 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -656,8 +656,10 @@
 
     @VisibleForTesting
     boolean isFrpActive() {
-        waitForInitDoneSignal();
         synchronized (mLock) {
+            // mFrpActive is initialized and automatic deactivation done (if possible) before the
+            // service is published, so there's no chance that callers could ask for the state
+            // before it has settled.
             return mFrpActive;
         }
     }
@@ -1253,6 +1255,7 @@
 
     private void enforceFactoryResetProtectionInactive() {
         if (mFrpEnforced && isFrpActive()) {
+            Slog.w(TAG, "Attempt to update PDB was blocked because FRP is active.");
             throw new SecurityException("FRP is active");
         }
     }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index af4da81..9b347d5 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -13854,7 +13854,9 @@
             mNumAllUidCpuTimeReads += 2;
         }
 
-        updateSystemServerThreadStats();
+        if (!Flags.disableSystemServicePowerAttr()) {
+            updateSystemServerThreadStats();
+        }
 
         if (powerAccumulator != null) {
             updateCpuEnergyConsumerStatsLocked(cpuClusterChargeUC, powerAccumulator);
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index c9805c7..09a177a 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -149,7 +149,7 @@
                     && mAdaptiveHapticsScales.size() > 0
                     && mAdaptiveHapticsScales.contains(usageHint)) {
                 float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
-                segment = segment.scale(adaptiveScale);
+                segment = segment.scaleLinearly(adaptiveScale);
             }
 
             segments.set(i, segment);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 764170a..c3efcb1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2228,8 +2228,9 @@
             if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
             SparseArray<Rect> relativeSuggestedCrops =
                     mWallpaperCropper.getRelativeCropHints(wallpaper);
-            Point croppedBitmapSize =
-                    new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+            Point croppedBitmapSize = new Point(
+                    (int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize),
+                    (int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize));
             SparseArray<Rect> relativeDefaultCrops =
                     mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize);
             SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ec3c122..eb618ef 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -102,6 +102,9 @@
 import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
 import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
@@ -4173,7 +4176,8 @@
             task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask(
                     t -> t.isActivityTypeStandard());
         }
-        if (task != null && task.getTopMostActivity() != null) {
+        if (task != null && task.getTopMostActivity() != null
+                && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) {
             mWindowManager.mAtmService.mActivityClientController.onPictureInPictureUiStateChanged(
                     task.getTopMostActivity(), pipState);
         }
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index a3e2869..b68e67e 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.content.ClipData;
 import android.content.Context;
 import android.hardware.input.InputManagerGlobal;
@@ -43,8 +44,8 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.window.IGlobalDragListener;
 import android.window.IUnhandledDragCallback;
-import android.window.IUnhandledDragListener;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
@@ -81,9 +82,9 @@
     private WindowManagerService mService;
     private final Handler mHandler;
 
-    // The unhandled drag listener for handling cross-window drags that end with no target window
-    private IUnhandledDragListener mUnhandledDragListener;
-    private final IBinder.DeathRecipient mUnhandledDragListenerDeathRecipient =
+    // The global drag listener for handling cross-window drags
+    private IGlobalDragListener mGlobalDragListener;
+    private final IBinder.DeathRecipient mGlobalDragListenerDeathRecipient =
             new IBinder.DeathRecipient() {
         @Override
         public void binderDied() {
@@ -91,7 +92,7 @@
                 if (hasPendingUnhandledDropCallback()) {
                     onUnhandledDropCallback(false /* consumedByListeners */);
                 }
-                setUnhandledDragListener(null);
+                setGlobalDragListener(null);
             }
         }
     };
@@ -129,29 +130,22 @@
     /**
      * Sets the listener for unhandled cross-window drags.
      */
-    public void setUnhandledDragListener(IUnhandledDragListener listener) {
-        if (mUnhandledDragListener != null && mUnhandledDragListener.asBinder() != null) {
-            mUnhandledDragListener.asBinder().unlinkToDeath(
-                    mUnhandledDragListenerDeathRecipient, 0);
+    public void setGlobalDragListener(IGlobalDragListener listener) {
+        if (mGlobalDragListener != null && mGlobalDragListener.asBinder() != null) {
+            mGlobalDragListener.asBinder().unlinkToDeath(
+                    mGlobalDragListenerDeathRecipient, 0);
         }
-        mUnhandledDragListener = listener;
+        mGlobalDragListener = listener;
         if (listener != null && listener.asBinder() != null) {
             try {
-                mUnhandledDragListener.asBinder().linkToDeath(
-                        mUnhandledDragListenerDeathRecipient, 0);
+                mGlobalDragListener.asBinder().linkToDeath(
+                        mGlobalDragListenerDeathRecipient, 0);
             } catch (RemoteException e) {
-                mUnhandledDragListener = null;
+                mGlobalDragListener = null;
             }
         }
     }
 
-    /**
-     * Returns whether there is an unhandled drag listener set.
-     */
-    boolean hasUnhandledDragListener() {
-        return mUnhandledDragListener != null;
-    }
-
     void sendDragStartedIfNeededLocked(WindowState window) {
         mDragState.sendDragStartedIfNeededLocked(window);
     }
@@ -351,7 +345,20 @@
 
                 final boolean relinquishDragSurfaceToDropTarget =
                         consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
+                final boolean isCrossWindowDrag = !mDragState.mLocalWin.equals(token);
                 mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
+
+                final Task droppedWindowTask = callingWin.getTask();
+                if (com.android.window.flags.Flags.delegateUnhandledDrags()
+                        && mGlobalDragListener != null && droppedWindowTask != null && consumed
+                        && isCrossWindowDrag) {
+                    try {
+                        mGlobalDragListener.onCrossWindowDrop(droppedWindowTask.getTaskInfo());
+                    } catch (RemoteException e) {
+                        Slog.e(TAG_WM, "Failed to call global drag listener for cross-window "
+                                + "drop", e);
+                    }
+                }
             }
         } finally {
             mCallback.get().postReportDropResult();
@@ -367,19 +374,19 @@
         final boolean isLocalDrag =
                 (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
         if (!com.android.window.flags.Flags.delegateUnhandledDrags()
-                || mUnhandledDragListener == null
+                || mGlobalDragListener == null
                 || isLocalDrag) {
             // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
             // purely local drag
             if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener "
-                    + "(listener=" + mUnhandledDragListener + ", flags=" + mDragState.mFlags + ")");
+                    + "(listener=" + mGlobalDragListener + ", flags=" + mDragState.mFlags + ")");
             return false;
         }
         if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
         try {
             // Schedule timeout for the unhandled drag listener to call back
             sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS);
-            mUnhandledDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
+            mGlobalDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
                 @Override
                 public void notifyUnhandledDropComplete(boolean consumedByListener) {
                     if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
@@ -390,7 +397,7 @@
             });
             return true;
         } catch (RemoteException e) {
-            Slog.e(TAG_WM, "Failed to call unhandled drag listener", e);
+            Slog.e(TAG_WM, "Failed to call global drag listener for unhandled drop", e);
             return false;
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8ebe826..9b7bc43 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -306,11 +306,11 @@
 import android.view.inputmethod.ImeTracker;
 import android.window.AddToSurfaceSyncGroupResult;
 import android.window.ClientWindowFrames;
+import android.window.IGlobalDragListener;
 import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
-import android.window.IUnhandledDragListener;
 import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
@@ -10020,14 +10020,13 @@
     }
 
     /**
-     * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
-     * (ie. not handled by any window which can handle the drag).
+     * Sets the listener to be called back when a cross-window drag and drop operation happens.
      */
     @Override
-    public void setUnhandledDragListener(IUnhandledDragListener listener) throws RemoteException {
+    public void setGlobalDragListener(IGlobalDragListener listener) throws RemoteException {
         mAtmService.enforceTaskPermission("setUnhandledDragListener");
         synchronized (mGlobalLock) {
-            mDragDropController.setUnhandledDragListener(listener);
+            mDragDropController.setGlobalDragListener(listener);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a8de919..d6fc01a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2254,6 +2254,11 @@
         ownerTask.addChild(taskFragment, position);
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
         if (!creationParams.getInitialRelativeBounds().isEmpty()) {
+            // The surface operations for the task fragment should sync with the transition.
+            // This avoid using pending transaction before collectExistenceChange is called.
+            if (transition != null) {
+                addToSyncSet(transition.getSyncId(), taskFragment);
+            }
             // Set relative bounds instead of using setBounds. This will avoid unnecessary update in
             // case the parent has resized since the last time parent info is sent to the organizer.
             taskFragment.setRelativeEmbeddedBounds(creationParams.getInitialRelativeBounds());
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index d821625..13cc534 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1510,8 +1510,8 @@
     switch (inputEvent.getType()) {
         case InputEventType::KEY:
             inputEventObj.reset(
-                    android_view_KeyEvent_fromNative(env,
-                                                     static_cast<const KeyEvent&>(inputEvent)));
+                    android_view_KeyEvent_obtainAsCopy(env,
+                                                       static_cast<const KeyEvent&>(inputEvent)));
             break;
         case InputEventType::MOTION:
             inputEventObj.reset(
@@ -1559,7 +1559,7 @@
 
     const nsecs_t when = keyEvent.getEventTime();
     JNIEnv* env = jniEnv();
-    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_obtainAsCopy(env, keyEvent));
     if (!keyEventObj.get()) {
         ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
         return;
@@ -1639,7 +1639,7 @@
 
     // Token may be null
     ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
-    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_obtainAsCopy(env, keyEvent));
     if (!keyEventObj.get()) {
         ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
         return 0;
@@ -1670,7 +1670,7 @@
 
     // Note: tokenObj may be null.
     ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
-    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_obtainAsCopy(env, keyEvent));
     if (!keyEventObj.get()) {
         ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
         return {};
@@ -1689,7 +1689,8 @@
         return {};
     }
 
-    const KeyEvent fallbackEvent = android_view_KeyEvent_toNative(env, fallbackKeyEventObj.get());
+    const KeyEvent fallbackEvent =
+            android_view_KeyEvent_obtainAsCopy(env, fallbackKeyEventObj.get());
     android_view_KeyEvent_recycle(env, fallbackKeyEventObj.get());
     return fallbackEvent;
 }
@@ -2070,7 +2071,7 @@
     InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
 
     if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
-        const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
+        const KeyEvent keyEvent = android_view_KeyEvent_obtainAsCopy(env, inputEventObj);
         const InputEventInjectionResult result =
                 im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
                                                                         std::chrono::milliseconds(
@@ -2101,7 +2102,7 @@
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
-        const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
+        const KeyEvent keyEvent = android_view_KeyEvent_obtainAsCopy(env, inputEventObj);
         std::unique_ptr<VerifiedInputEvent> verifiedEvent =
                 im->getInputManager()->getDispatcher().verifyInputEvent(keyEvent);
         if (verifiedEvent == nullptr) {
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 23aa374..96ef2ed 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -531,7 +531,7 @@
             int index = 0;
             for (CandidateBrowsingPhaseMetric metric : browsingPhaseMetrics) {
                 browsedClickedEntries[index] = metric.getEntryEnum();
-                browsedProviderUid[index] = metric.getProviderUid();
+                browsedProviderUid[index] = DEFAULT_INT_32;
                 index++;
             }
             FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINALNOUID_REPORTED,
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
new file mode 100644
index 0000000..e42bdad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "postsubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.rollback"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index dfda1fc..3bc089f 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -662,7 +662,7 @@
             android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
     public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers()
             throws IOException {
-        String pkgName = "com.example.app";
+        String packageName = "com.example.app";
         String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
                 + "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
 
@@ -670,7 +670,7 @@
                 .toByteArray();
         String contents = "<config>"
                 + "<" + "enhanced-confirmation-trusted-installer" + " "
-                + "package=\"" + pkgName + "\""
+                + "package=\"" + packageName + "\""
                 + " sha256-cert-digest=\"" + certificateDigestStr + "\""
                 + "/>"
                 + "</config>";
@@ -684,10 +684,10 @@
 
         assertThat(actualTrustedInstallers.size()).isEqualTo(1);
         SignedPackage actual = actualTrustedInstallers.stream().findFirst().orElseThrow();
-        SignedPackage expected = new SignedPackage(pkgName, certificateDigest);
+        SignedPackage expected = new SignedPackage(packageName, certificateDigest);
 
         assertThat(actual.getCertificateDigest()).isEqualTo(expected.getCertificateDigest());
-        assertThat(actual.getPkgName()).isEqualTo(expected.getPkgName());
+        assertThat(actual.getPackageName()).isEqualTo(expected.getPackageName());
         assertThat(actual).isEqualTo(expected);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 266a0d5c..4c32a58 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -67,7 +67,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.window.IUnhandledDragListener;
+import android.window.IGlobalDragListener;
 
 import androidx.test.filters.SmallTest;
 
@@ -544,9 +544,9 @@
     public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
-        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
-        mTarget.setUnhandledDragListener(listener);
+        mTarget.setGlobalDragListener(listener);
         doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
         verify(listener, times(0)).onUnhandledDrop(any(), any());
     }
@@ -555,9 +555,9 @@
     public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
-        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
-        mTarget.setUnhandledDragListener(listener);
+        mTarget.setGlobalDragListener(listener);
         final int invalidXY = 100_000;
         startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
             // Notify the unhandled drag listener
@@ -578,9 +578,9 @@
     public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
-        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
-        mTarget.setUnhandledDragListener(listener);
+        mTarget.setGlobalDragListener(listener);
         final int invalidXY = 100_000;
         startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
             // Notify the unhandled drag listener
@@ -600,9 +600,9 @@
     public void testUnhandledDragListenerCallbackTimeout() {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
-        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
-        mTarget.setUnhandledDragListener(listener);
+        mTarget.setGlobalDragListener(listener);
         final int invalidXY = 100_000;
         startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
             // Notify the unhandled drag listener
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index f628af1..452c98c 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -296,6 +296,10 @@
         clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
     }
 
+    fun setSourceRectHint() {
+        clickObject(SOURCE_RECT_HINT)
+    }
+
     fun checkWithCustomActionsCheckbox() =
         uiDevice
             .findObject(By.res(packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
@@ -444,6 +448,7 @@
         private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
         private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
         private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
+        private const val SOURCE_RECT_HINT = "set_source_rect_hint"
         // minimum number of steps to take, when animating gestures, needs to be 2
         // so that there is at least a single intermediate layer that flicker tests can check
         private const val MIN_STEPS_TO_ANIMATE = 2
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index f7ba45b..36cbf1a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -27,6 +27,15 @@
          where things are arranged differently and to circle back up to the top once we reach the
          bottom. -->
 
+    <!-- View used for testing sourceRectHint. -->
+    <View
+        android:id="@+id/source_rect"
+        android:layout_width="320dp"
+        android:layout_height="180dp"
+        android:visibility="gone"
+        android:background="@android:color/holo_green_light"
+        />
+
     <Button
         android:id="@+id/enter_pip"
         android:layout_width="wrap_content"
@@ -113,6 +122,13 @@
             android:onClick="onRatioSelected"/>
     </RadioGroup>
 
+    <Button
+        android:id="@+id/set_source_rect_hint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Set SourceRectHint"
+        android:onClick="setSourceRectHint"/>
+
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 12eaad1..1ab8ddb 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.media.MediaMetadata;
 import android.media.session.MediaSession;
@@ -45,6 +46,7 @@
 import android.util.Log;
 import android.util.Rational;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.CheckBox;
@@ -248,6 +250,29 @@
         }
     }
 
+    /**
+     * Adds a temporary view used for testing sourceRectHint.
+     *
+     */
+    public void setSourceRectHint(View v) {
+        View rectView = findViewById(R.id.source_rect);
+        if (rectView != null) {
+            rectView.setVisibility(View.VISIBLE);
+            rectView.getViewTreeObserver().addOnGlobalLayoutListener(
+                    new ViewTreeObserver.OnGlobalLayoutListener() {
+                        @Override
+                        public void onGlobalLayout() {
+                            Rect boundingRect = new Rect();
+                            rectView.getGlobalVisibleRect(boundingRect);
+                            mPipParamsBuilder.setSourceRectHint(boundingRect);
+                            setPictureInPictureParams(mPipParamsBuilder.build());
+                            rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        }
+                    });
+            rectView.invalidate(); // changing the visibility, invalidating to redraw the view
+        }
+    }
+
     public void onRatioSelected(View v) {
         switch (v.getId()) {
             case R.id.ratio_default:
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index 228520e..bc1df75 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -3,6 +3,7 @@
 //########################################################################
 
 package {
+    default_team: "trendy_team_enigma",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/wifi/TEST_MAPPING b/wifi/TEST_MAPPING
index 757ecaa..3ae91be 100644
--- a/wifi/TEST_MAPPING
+++ b/wifi/TEST_MAPPING
@@ -2,9 +2,7 @@
   "presubmit": [
     {
       "name": "FrameworksWifiNonUpdatableApiTests"
-    }
-  ],
-  "postsubmit": [
+    },
     {
       "name": "CtsWifiNonUpdatableTestCases"
     }